Public
Edited
Jul 2
Insert cell
Insert cell
G.img
Insert cell
G = {
const img = await FileAttachment("waldo.jpg").image();
return {
initialValue: { x: 300, y: 200 },
sizes: { x: 900, y: 400 },
radius: 80,
img,
duration: 1000
};
}
Insert cell
Insert cell
viewof portalPosition = {
const initialValue = G.initialValue;
const input = Inputs.button("Transport to New Location", {
reduce: () => ({
x: Math.floor(Math.random() * (G.sizes.x - 200) + 100), // Keep portal radius away from edges
y: Math.floor(Math.random() * (G.sizes.y - 200) + 100) // 100px margin on each side
})
});
input.value = initialValue;
return input;
}
Insert cell
{
if (!G.img) return html`<div>Loading image...</div>`;
const canvas = DOM.canvas(G.sizes.x, G.sizes.y);
const ctx = canvas.getContext("2d");

// Initialize starting position only once
if (!window.portalLastPosition) {
window.portalLastPosition = { x: 300, y: 200 }; // or portalPosition initial value
}

// Start new animation - Observable reactivity triggers this when portalPosition changes
const startTime = Date.now();
const targetPosition = { x: portalPosition.x, y: portalPosition.y };

// Easing function for smooth animation
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
const duration = G.duration;

function animate() {
const elapsed = Date.now() - startTime;
const t = Math.min(elapsed / duration, 1);
const progress = easeOutCubic(t);

const currentX =
window.portalLastPosition.x +
(targetPosition.x - window.portalLastPosition.x) * progress;
const currentY =
window.portalLastPosition.y +
(targetPosition.y - window.portalLastPosition.y) * progress;

// the "rendering pipeline" yeah right...
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 0.4;
ctx.drawImage(G.img, 0, 0, canvas.width, canvas.height);

ctx.save();
ctx.globalAlpha = 1.0;
ctx.beginPath();
ctx.arc(currentX, currentY, G.radius, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(G.img, 0, 0, canvas.width, canvas.height);
ctx.restore();

if (t < 1) {
requestAnimationFrame(animate);
} else {
// Animation complete - update last position for next animation
window.portalLastPosition = { x: targetPosition.x, y: targetPosition.y };
}
}

animate();
return canvas;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more