Skip to content

Instantly share code, notes, and snippets.

@anthonyec
Last active June 25, 2023 22:47
Show Gist options
  • Save anthonyec/90f43bc21fa0231980666e560d083466 to your computer and use it in GitHub Desktop.
Save anthonyec/90f43bc21fa0231980666e560d083466 to your computer and use it in GitHub Desktop.
Draw a region and mark positions

Poster markup

A tiny tool to let you draw regions, mark positions inside the regions and export the positions as percentage coordinates.

How to use

Copy and paste all the code from poster_markup.js into the dev console, and press enter.

//
// ## Globals ##
//
let currentState = null;
let mouse = {
target: null,
down: false,
justPressed: false,
x: 0,
y: 0,
startX: 0,
startY: 0,
};
//
// ## Helper functions to create DOM elements ##
//
function createOverlayElement() {
const overlay = document.createElement("div");
overlay.style.position = "fixed";
overlay.style.background = "rgba(200, 200, 200, 0.2)";
overlay.style.width = "100%";
overlay.style.height = "100%";
overlay.style.top = "0";
overlay.style.left = "0";
overlay.style.cursor = "crosshair";
overlay.style.fontFamily = "sans-serif";
overlay.style.zIndex = "9999";
return overlay;
}
function createInstructionsElement(
text = "",
backgroundColor = "rgba(0, 200, 0, 1)"
) {
const instructions = document.createElement("div");
instructions.style.background = backgroundColor;
instructions.style.position = "absolute";
instructions.style.padding = "10px";
instructions.style.width = "100%";
instructions.style.color = "white";
instructions.style.display = "flex";
instructions.style.boxSizing = "border-box";
instructions.style.justifyContent = "space-between";
instructions.textContent = text;
return instructions;
}
function createRegionElement(
bounds = { x: 0, y: 0, width: 0, height: 0 },
disablePointerEvents = true
) {
const region = document.createElement("div");
region.style.position = "absolute";
region.style.boxShadow = "0 0 0 5px rgba(0, 200, 0, 0.8)";
region.style.left = bounds.x + "px";
region.style.top = bounds.y + "px";
region.style.width = bounds.width + "px";
region.style.height = bounds.height + "px";
if (disablePointerEvents) {
region.style.pointerEvents = "none";
}
return region;
}
//
// ## App states ##
//
const initialState = {
enter() {
this.startButton = document.createElement("button");
this.startButton.textContent = "Start";
this.startButton.style.position = "fixed";
this.startButton.style.zIndex = "99999";
this.startButton.style.top = 0;
this.startButton.style.left = 0;
this.startButton.addEventListener("click", () => {
transitionTo("drawRegion");
});
document.body.appendChild(this.startButton);
},
exit() {
document.body.removeChild(this.startButton);
},
update() {},
};
const drawRegion = {
// State specfic variables.
showRegion: false,
regionBounds: { x: 0, y: 0, width: 0, height: 0 },
enter() {
this.regionBounds = { x: 0, y: 0, width: 0, height: 0 };
this.overlay = createOverlayElement();
this.region = createRegionElement(this.regionBounds, false);
this.instructions = createInstructionsElement(
"Click and drag to draw a region around the poster."
);
this.confirmButton = document.createElement("button");
this.confirmButton.textContent = "Next";
this.region.addEventListener("click", () => {
transitionTo("markPositions", {
regionBounds: this.regionBounds,
});
});
this.confirmButton.addEventListener("click", () => {
transitionTo("markPositions", {
regionBounds: this.regionBounds,
});
});
this.overlay.appendChild(this.region);
this.overlay.appendChild(this.instructions);
this.instructions.appendChild(this.confirmButton);
document.body.appendChild(this.overlay);
},
exit() {
document.body.removeChild(this.overlay);
},
update() {
const regionBounds = this.region.getBoundingClientRect();
this.showRegion = regionBounds.width > 0 && regionBounds.height > 0;
this.region.style.opacity = this.showRegion ? "1" : "0";
if (mouse.down && mouse.target === this.overlay) {
this.region.style.left = mouse.startX + "px";
this.region.style.top = mouse.startY + "px";
this.region.style.width = mouse.x - mouse.startX + "px";
this.region.style.height = mouse.y - mouse.startY + "px";
}
this.regionBounds = regionBounds;
},
};
const markPositions = {
positions: [],
enter(params = {}) {
this.positions = [];
this.overlay = createOverlayElement();
this.region = createRegionElement(params.regionBounds, false);
this.instructions = createInstructionsElement(
"Click inside the region to mark positions. Click marks to remove them.",
"rgba(150, 150, 150, 1)"
);
this.exportButton = document.createElement("button");
this.exportButton.textContent = "Export and close";
this.exportButton.addEventListener("click", () => {
// TODO: Implement the format the data should be exported
// to. Could be a .csv file?
console.log(this.positions);
transitionTo("initialState");
});
this.region.addEventListener("mousedown", (event) => {
if (event.target !== this.region) {
return;
}
const id = Date.now() + "-" + Math.round(Math.random() * 100);
const x = event.offsetX;
const y = event.offsetY;
const percentX = x / params.regionBounds.width;
const percentY = y / params.regionBounds.height;
this.positions.push({ id, x: percentX, y: percentY });
const marker = document.createElement("div");
marker.style.position = "absolute";
marker.style.background = "red";
marker.style.border = "2px solid white";
marker.style.boxShadow = "0 0 0 2px red";
marker.style.borderRadius = "100px";
marker.style.width = "8px";
marker.style.height = "8px";
marker.style.transform = "translate(-50%, -50%)";
marker.style.left = percentX * 100 + "%";
marker.style.top = percentY * 100 + "%";
marker.addEventListener("click", () => {
const positionToRemoveIndex = this.positions.findIndex(
(position) => position.id === id
);
this.positions.splice(positionToRemoveIndex, 1);
this.region.removeChild(marker);
});
this.region.appendChild(marker);
});
this.instructions.appendChild(this.exportButton);
this.overlay.appendChild(this.region);
this.overlay.appendChild(this.instructions);
document.body.appendChild(this.overlay);
},
exit() {
document.body.removeChild(this.overlay);
},
update() {},
};
//
// ## State machine ##
//
const states = {
initialState,
drawRegion,
markPositions,
};
function transitionTo(stateName, params = {}) {
const nextState = states[stateName];
if (!nextState) {
console.warn("No state called", stateName);
return;
}
if (currentState && currentState["exit"]) {
currentState.exit();
}
currentState = states[stateName];
if (currentState && currentState["enter"]) {
currentState.enter(params);
}
}
function update() {
if (!currentState || !currentState["update"]) {
console.warn("No current state");
return;
}
currentState.update();
window.requestAnimationFrame(update);
}
//
// ## Initialize ##
//
transitionTo("initialState");
update();
document.addEventListener("mousemove", (event) => {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
document.addEventListener("mousedown", (event) => {
mouse.down = true;
mouse.target = event.target;
mouse.startX = event.clientX;
mouse.startY = event.clientY;
});
document.addEventListener("mouseup", () => {
mouse.down = false;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment