Skip to content

Instantly share code, notes, and snippets.

@tobyshooters
Last active April 23, 2021 21:59
Show Gist options
  • Save tobyshooters/12b097dbf68b7b3093578d38f7b45579 to your computer and use it in GitHub Desktop.
Save tobyshooters/12b097dbf68b7b3093578d38f7b45579 to your computer and use it in GitHub Desktop.
infinite, multimedia canvas
<style>
html, body {
margin: 0px;
background-color: #fdf6e3;
font-family: monospace;
}
#canvas {
height: 100%;
width: 100%;
}
.draggableItem {
position: absolute;
user-select: none;
user-drag: none;
-webkit-user-drag: none;
-moz-user-drag: none;
}
</style>
<div id="canvas"></div>
<script>
const getPosition = (elem) => {
// This doesn't work with textarea...
// const rect = elem.getBoundingClientRect()
// return [rect.x, rect.y];
const left = parseInt(elem.style.left.slice(0, -2));
const top = parseInt(elem.style.top.slice(0, -2));
return [left, top];
}
const setPosition = (elem, x, y) => {
elem.style.top = `${y}px`;
elem.style.left = `${x}px`;
}
const movePosition = (elem, dx, dy) => {
const [x, y] = getPosition(elem);
setPosition(elem, x + dx, y + dy);
}
const MAX_Z_INDEX = 99999;
const decrementZ = (elem) => {
elem.style.zIndex = (elem.style.zIndex || MAX_Z_INDEX) - 1;
}
const popToFront = (elem, otherElements) => {
otherElements.map(decrementZ);
elem.style.zIndex = MAX_Z_INDEX;
}
const makeElementDraggable = (elem, otherElements) => {
// References:
// 1. htmldom.dev/make-a-draggable-element
// 2. developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events
// e.target also would work. However, if the cursor ever escapes from
// the boundary of the object (e.g. fast movement) then the target
// switches to whatever is beneath it. Binding `elem` is better.
let x, y;
const mouseDown = (e) => {
if (e.shiftKey || e.metaKey) {
e.preventDefault();
e.stopPropagation(); // Don't call parent's mouseDown
x = e.clientX;
y = e.clientY;
popToFront(elem, otherElements);
document.addEventListener('mousemove', mouseMove);
document.addEventListener('mouseup', mouseUp);
}
}
const mouseMove = (e) => {
const dx = e.clientX - x;
const dy = e.clientY - y;
if (e.shiftKey) {
movePosition(elem, dx, dy);
} else if (e.metaKey) {
elem.width = parseInt(elem.width) + dx;
elem.height = parseInt(elem.height) + dy;
}
x = e.clientX;
y = e.clientY;
}
const mouseUp = (e) => {
document.removeEventListener('mousemove', mouseMove);
document.removeEventListener('mouseup', mouseUp);
}
elem.addEventListener('mousedown', mouseDown);
elem.addEventListener("dblclick", (e) => {
e.preventDefault();
e.stopPropagation();
if (e.metaKey) { elem.remove(); }
})
}
const makeBackgroundDraggable = (canvas, elements) => {
let x, y;
const mouseDown = (e) => {
if (e.shiftKey) {
e.preventDefault();
e.stopPropagation();
x = e.clientX;
y = e.clientY;
document.addEventListener('mousemove', mouseMove);
document.addEventListener('mouseup', mouseUp);
}
}
const mouseMove = (e) => {
const dx = e.clientX - x;
const dy = e.clientY - y;
elements.map(elem => movePosition(elem, dx, dy))
x = e.clientX;
y = e.clientY;
}
const mouseUp = () => {
document.removeEventListener('mousemove', mouseMove);
document.removeEventListener('mouseup', mouseUp);
}
canvas.addEventListener('mousedown', mouseDown);
}
const canvas = document.getElementById("canvas");
const elements = [];
makeBackgroundDraggable(canvas, elements);
const addToCanvas = (elem) => {
elem.classList.add("draggableItem");
makeElementDraggable(elem, elements);
// NOTE: pushing to `elements` makes item move with background.
// Only works due to Javascript's lexical scope. Since the original
// draggable functions reference `elements`, pushing to the array
// is sufficient to make all other elements aware of it.
elements.push(elem);
canvas.appendChild(elem);
popToFront(elem, otherElements);
}
canvas.addEventListener('dblclick', (e) => {
var textInput = document.createElement("textarea");
setPosition(textInput, e.clientX, e.clientY);
textInput.style.width = 200;
textInput.style.outline = 'none';
textInput.style.backgroundColor = 'rgba(0, 0, 0, 0)';
textInput.style.borderStyle = 'dashed';
textInput.style.borderWidth = '1px';
textInput.style.padding = '8px';
textInput.style.fontSize = '18px';
addToCanvas(textInput);
})
canvas.addEventListener('dragover', e => e.preventDefault());
canvas.addEventListener('drop', (e) => {
e.preventDefault();
if (e.dataTransfer.types[0] !== "Files") return;
const file = e.dataTransfer.items[0].getAsFile();
const reader = new FileReader();
reader.onload = () => {
if (["image/png", "image/jpg"].includes(file.type)) {
var elem = document.createElement('img');
} else if (["video/mp4", "video/quicktime"].includes(file.type)) {
var elem = document.createElement('video');
elem.controls = true;
} else if (["audio/mpeg"].includes(file.type)) {
var elem = document.createElement('audio');
elem.controls = true;
} else if (["application/pdf"].includes(file.type)) {
var elem = document.createElement('embed');
elem.type = "application/pdf";
elem.height = 1.29 * 400;
}
elem.src = reader.result;
elem.width = 400;
setPosition(elem, e.clientX, e.clientY);
addToCanvas(elem);
}
reader.readAsDataURL(file);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment