我有一个全屏背景画布,我想在该画布上绘制用作遮罩的元素。因此画布只能通过覆盖的元素可见。
左图中是我目前拥有的,其中渐变背景充当画布元素。右图是我试图实现的目标,其中有 2 个 div(掩码,掩码 2)。此外,2 个元素(蒙版、蒙版 2)应该是可拖动的,因此如果元素移动,背景仍将与画布显示的内容匹配。
我尝试使用遮罩、剪辑路径来做到这一点,但我似乎无法让它工作。 获得所需结果的最佳方法是什么?
最简单的可能是使用 CSS 来屏蔽你的输出
<canvas>
。mask
属性,并为每个元素使用一个 linear-gradient
作为 mask-image
来执行此操作。
const canvas = document.querySelector("canvas");
const elements = [...document.querySelectorAll(".resizeable")];
// Set one mask image per element
const image = elements.map(() => `linear-gradient(black, black)`).join(",");
canvas.style.setProperty("-webkit-mask-image", image);
canvas.style.setProperty("mask-image", image);
const updateMask = () => {
// Get the updated bounding rect of every elements
const rects = elements.map((el) => el.getBoundingClientRect());
const position = rects.map(({left, top}) => `${left}px ${top}px`).join(",");
const size = rects.map(({width, height}) => `${width}px ${height}px`).join(",");
// Update the mask properties
canvas.style.setProperty("-webkit-mask-position", position);
canvas.style.setProperty("mask-position", position);
canvas.style.setProperty("-webkit-mask-size", size);
canvas.style.setProperty("mask-size", size);
};
// Handle resizing of our elements
const observer = new ResizeObserver(() => updateMask());
elements.forEach((el) => observer.observe(el));
// Handle dragging of our elements
let dragged = null;
const offset = { x: 0, y: 0 };
document.addEventListener("mousedown", (evt) => {
if (evt.target.matches(".resizeable")) {
return;
}
dragged = evt.target.closest(".resizeable");
offset.x = evt.offsetX;
offset.y = evt.offsetY;
if (dragged) {
evt.preventDefault();
}
});
document.addEventListener("mousemove", (evt) => {
if (dragged) {
dragged.style.setProperty("--left", evt.clientX - offset.x);
dragged.style.setProperty("--top", evt.clientY - offset.y);
updateMask();
}
});
document.addEventListener("mouseup", (evt) => {
dragged = null;
});
// Render some noise on the canvas
const ctx = canvas.getContext("2d");
const img = new ImageData(1000, 1000);
const data = new Uint32Array(img.data.buffer);
ctx.fillStyle = ctx.createLinearGradient(0, 0, 1000, 0);
ctx.fillStyle.addColorStop(0, "orange");
ctx.fillStyle.addColorStop(0.2, "blue");
ctx.fillStyle.addColorStop(0.4, "green");
ctx.fillStyle.addColorStop(0.6, "orange");
ctx.fillStyle.addColorStop(0.8, "#F0F");
ctx.globalCompositeOperation = "color";
const anim = () => {
for (let i = 0; i<data.length; i++) {
data[i] = (Math.random() * 0xFFFFFF) + 0xFF000000;
}
ctx.putImageData(img, 0, 0);
ctx.fillRect(0, 0, 1000, 1000);
requestAnimationFrame(anim);
};
requestAnimationFrame(anim);
.resizeable {
position: absolute;
border: 1px solid;
overflow: hidden;
resize: both;
width: 300px;
height: 250px;
left: calc(var(--left) * 1px);
top: calc(var(--top) * 1px);
}
.resizeable:nth-of-type(2) {
--left: 350;
--top: 120;
}
.draggable {
width: 100%;
height: 100%;
}
canvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
/* Do not forget to disable the mask-repeat */
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
<canvas width=1000 height=1000></canvas>
<div class=resizeable><div class=draggable>foo bar</div></div>
<div class=resizeable><div class=draggable>baz bla</div></div>