如何获得CSS 3D转换画布的画布相对鼠标位置? [英] How to get a canvas relative mouse position of a CSS 3D transformed canvas?
问题描述
只是为了好玩,我试图在3D变换的画布上进行绘制.我写了一些代码,并且可以正常工作
const m4 = twgl.m4;
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', (e) => {
const pos = getElementRelativeMousePosition(e, canvas);
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(pos.x - 1, pos.y - 1, 3, 3);
});
});
function getElementRelativeMousePosition(e, elem) {
const pos = convertPointFromPageToNode(elem, e.pageX, e.pageY);
return {
x: pos[0],
y: pos[1],
};
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
function convertPointFromPageToNode(elem, pageX, pageY) {
const mat = m4.inverse(getTransformationMatrix(elem));
return m4.transformPoint(mat, [pageX, pageY, 0]);
};
function getTransformationMatrix(elem) {
let matrix = m4.identity();
let currentElem = elem;
while (currentElem !== undefined &&
currentElem !== currentElem.ownerDocument.documentElement) {
const style = window.getComputedStyle(currentElem);
const localMatrix = parseMatrix(style.transform);
matrix = m4.multiply(localMatrix, matrix);
currentElem = currentElem.parentElement;
}
const w = elem.offsetWidth;
const h = elem.offsetHeight;
let i = 4;
let left = +Infinity;
let top = +Infinity;
for (let i = 0; i < 4; ++i) {
const p = m4.transformPoint(matrix, [w * (i & 1), h * ((i & 2) >> 1), 0]);
left = Math.min(p[0], left);
top = Math.min(p[1], top);
}
const rect = elem.getBoundingClientRect()
document.querySelector('p').textContent =
`${w}x${h}`;
matrix = m4.multiply(m4.translation([
window.pageXOffset + rect.left - left,
window.pageYOffset + rect.top - top,
0]), matrix);
return matrix;
}
function parseMatrix(str) {
if (str.startsWith('matrix3d(')) {
return str.substring(9, str.length - 1).split(',').map(v => parseFloat(v.trim()));
} else if (str.startsWith('matrix(')) {
const m = str.substring(7, str.length - 1).split(',').map(v => parseFloat(v.trim()));
return [
m[0], m[1], 0, 0,
m[2], m[3], 0, 0,
0, 0, 1, 0,
m[4], m[5], 0, 1,
]
} else if (str == 'none') {
return m4.identity();
}
throw new Error('unknown format');
}
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c1 {
margin: 20px;
background: red;
transform: translateX(-50px);
display: inline-block;
}
#c2 {
margin: 20px;
background: green;
transform: rotate(45deg);
display: inline-block;
}
#c3 {
margin: 20px;
background: blue;
display: inline-block;
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg);
display: inline-block;
}
<p>
foo
</p>
<div id="c1">
<div id="c2">
<div id="c3">
<canvas></canvas>
</div>
</div>
</div>
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
上面的代码有效.将鼠标移到两个黄色画布元素上,您会看到它正确绘制了.
但是,一旦我添加了一些3D变换,它就会失败.
将#c6"的CSS更改为
#c6 {
background: pink;
transform: rotate(45deg) rotateX(45deg); /* changed */
display: inline-block;
}
现在,当我在右边的黄色画布上绘制时,一切都消失了.
const m4 = twgl.m4;
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', (e) => {
const pos = getElementRelativeMousePosition(e, canvas);
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(pos.x - 1, pos.y - 1, 3, 3);
});
});
function getElementRelativeMousePosition(e, elem) {
const pos = convertPointFromPageToNode(elem, e.pageX, e.pageY);
return {
x: pos[0],
y: pos[1],
};
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
function convertPointFromPageToNode(elem, pageX, pageY) {
const mat = m4.inverse(getTransformationMatrix(elem));
return m4.transformPoint(mat, [pageX, pageY, 0]);
};
function getTransformationMatrix(elem) {
let matrix = m4.identity();
let currentElem = elem;
while (currentElem !== undefined &&
currentElem !== currentElem.ownerDocument.documentElement) {
const style = window.getComputedStyle(currentElem);
const localMatrix = parseMatrix(style.transform);
matrix = m4.multiply(localMatrix, matrix);
currentElem = currentElem.parentElement;
}
const w = elem.offsetWidth;
const h = elem.offsetHeight;
let i = 4;
let left = +Infinity;
let top = +Infinity;
for (let i = 0; i < 4; ++i) {
const p = m4.transformPoint(matrix, [w * (i & 1), h * ((i & 2) >> 1), 0]);
left = Math.min(p[0], left);
top = Math.min(p[1], top);
}
const rect = elem.getBoundingClientRect()
document.querySelector('p').textContent =
`${w}x${h}`;
matrix = m4.multiply(m4.translation([
window.pageXOffset + rect.left - left,
window.pageYOffset + rect.top - top,
0]), matrix);
return matrix;
}
function parseMatrix(str) {
if (str.startsWith('matrix3d(')) {
return str.substring(9, str.length - 1).split(',').map(v => parseFloat(v.trim()));
} else if (str.startsWith('matrix(')) {
const m = str.substring(7, str.length - 1).split(',').map(v => parseFloat(v.trim()));
return [
m[0], m[1], 0, 0,
m[2], m[3], 0, 0,
0, 0, 1, 0,
m[4], m[5], 0, 1,
]
} else if (str == 'none') {
return m4.identity();
}
throw new Error('unknown format');
}
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c1 {
margin: 20px;
background: red;
transform: translateX(-50px);
display: inline-block;
}
#c2 {
margin: 20px;
background: green;
transform: rotate(45deg);
display: inline-block;
}
#c3 {
margin: 20px;
background: blue;
display: inline-block;
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg) rotateX(45deg);
display: inline-block;
}
<p>
foo
</p>
<div id="c1">
<div id="c2">
<div id="c3">
<canvas></canvas>
</div>
</div>
</div>
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
有什么想法我在做什么错吗?
注意:这只是OP本身已经发现的补充答案.
实际上,您可以使用 MouseEvent来完成所有工作构造函数.
您可以在构造函数内部传递此Event的clientX
和clientY
属性(或者,如果愿意,可以传递pageX
& pageY
),然后将此组合事件分派给目标将设置其offsetX
和相对于目标的offsetY
属性.
由于dispatchEvent确实会同步触发该事件,因此我们甚至可以创建一个转换器:
const init_pos = { x: 50, y: 50};
const relative_pos = {};
const canvas = document.querySelector('canvas');
canvas.addEventListener('mousemove', e => {
relative_pos.x = e.offsetX;
relative_pos.y = e.offsetY;
}, {once: true});
canvas.dispatchEvent(new MouseEvent('mousemove', {
clientX: init_pos.x,
clientY: init_pos.y
}));
// synchronously log
console.log(relative_pos);
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg);
display: inline-block;
}
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
现在,给出您自己的答案中的示例,您可能希望实际持有一个将保留全局Event位置的对象,并在requestAnimationFrame
循环中的每一帧获取画布的相对位置. br>
但是,此设置显然会遍历您的画布,如果您只希望可见的面孔来处理事件,则必须检查哪个匹配document.elementFromPoint(x, y)
,哪个本身需要您的元素来响应指针事件.>
// will hold our last event's position
const pos = {
x: 0,
y: 0
};
const canvases = document.querySelectorAll('canvas');
// A single global "real" MouseEvent handler
document.body.onmousemove = (e) => {
pos.x = e.clientX;
pos.y = e.clientY;
};
canvases.forEach(canvas => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', draw);
function draw(e) {
// do not fire on real Events
if (e.cancelable) return;
const x = e.offsetX * canvas.width / canvas.clientWidth;
const y = e.offsetY * canvas.height / canvas.clientHeight;
if (x < 0 || x > canvas.width || y < 0 || y > canvas.height) {
return;
}
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(x - 1, y - 1, 3, 3);
}
});
anim();
function anim() {
requestAnimationFrame(anim);
// in case we want to paint only on the front element
const front_elem = single_face.checked && document.elementFromPoint(pos.x, pos.y);
// at every frame
canvases.forEach(c => {
if (!front_elem || c === front_elem) {
// force a composed event (synchronously, so we are still in rAF callback)
c.dispatchEvent(
new MouseEvent('mousemove', {
clientX: pos.x,
clientY: pos.y
})
);
}
});
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
.scene {
width: 200px;
height: 200px;
perspective: 600px;
}
.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
animation-duration: 16s;
animation-name: rotate;
animation-iteration-count: infinite;
animation-timing-function: linear;
pointer-events: none; /* no need for mouse events */
}
#single_face:checked+.scene .cube {
pointer-events: all; /* except if we want to find out who is the front one */
}
label,#single_face {float: right}
@keyframes rotate {
from {
transform: translateZ(-100px) rotateX( 0deg) rotateY( 0deg);
}
to {
transform: translateZ(-100px) rotateX(360deg) rotateY(720deg);
}
}
.cube__face {
position: absolute;
width: 200px;
height: 200px;
display: block;
}
.cube__face--front {
background: rgba(255, 0, 0, 0.2);
transform: rotateY( 0deg) translateZ(100px);
}
.cube__face--right {
background: rgba(0, 255, 0, 0.2);
transform: rotateY( 90deg) translateZ(100px);
}
.cube__face--back {
background: rgba(0, 0, 255, 0.2);
transform: rotateY(180deg) translateZ(100px);
}
.cube__face--left {
background: rgba(255, 255, 0, 0.2);
transform: rotateY(-90deg) translateZ(100px);
}
.cube__face--top {
background: rgba(0, 255, 255, 0.2);
transform: rotateX( 90deg) translateZ(100px);
}
.cube__face--bottom {
background: rgba(255, 0, 255, 0.2);
transform: rotateX(-90deg) translateZ(100px);
}
<label>Draw on a single face</label><input type="checkbox" id="single_face">
<div class="scene">
<div class="cube">
<canvas class="cube__face cube__face--front"></canvas>
<canvas class="cube__face cube__face--back"></canvas>
<canvas class="cube__face cube__face--right"></canvas>
<canvas class="cube__face cube__face--left"></canvas>
<canvas class="cube__face cube__face--top"></canvas>
<canvas class="cube__face cube__face--bottom"></canvas>
</div>
</div>
<pre id="debug"></pre>
Just for fun I'm trying to draw on 3D transformed canvases. I wrote some code and it kind of works
const m4 = twgl.m4;
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', (e) => {
const pos = getElementRelativeMousePosition(e, canvas);
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(pos.x - 1, pos.y - 1, 3, 3);
});
});
function getElementRelativeMousePosition(e, elem) {
const pos = convertPointFromPageToNode(elem, e.pageX, e.pageY);
return {
x: pos[0],
y: pos[1],
};
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
function convertPointFromPageToNode(elem, pageX, pageY) {
const mat = m4.inverse(getTransformationMatrix(elem));
return m4.transformPoint(mat, [pageX, pageY, 0]);
};
function getTransformationMatrix(elem) {
let matrix = m4.identity();
let currentElem = elem;
while (currentElem !== undefined &&
currentElem !== currentElem.ownerDocument.documentElement) {
const style = window.getComputedStyle(currentElem);
const localMatrix = parseMatrix(style.transform);
matrix = m4.multiply(localMatrix, matrix);
currentElem = currentElem.parentElement;
}
const w = elem.offsetWidth;
const h = elem.offsetHeight;
let i = 4;
let left = +Infinity;
let top = +Infinity;
for (let i = 0; i < 4; ++i) {
const p = m4.transformPoint(matrix, [w * (i & 1), h * ((i & 2) >> 1), 0]);
left = Math.min(p[0], left);
top = Math.min(p[1], top);
}
const rect = elem.getBoundingClientRect()
document.querySelector('p').textContent =
`${w}x${h}`;
matrix = m4.multiply(m4.translation([
window.pageXOffset + rect.left - left,
window.pageYOffset + rect.top - top,
0]), matrix);
return matrix;
}
function parseMatrix(str) {
if (str.startsWith('matrix3d(')) {
return str.substring(9, str.length - 1).split(',').map(v => parseFloat(v.trim()));
} else if (str.startsWith('matrix(')) {
const m = str.substring(7, str.length - 1).split(',').map(v => parseFloat(v.trim()));
return [
m[0], m[1], 0, 0,
m[2], m[3], 0, 0,
0, 0, 1, 0,
m[4], m[5], 0, 1,
]
} else if (str == 'none') {
return m4.identity();
}
throw new Error('unknown format');
}
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c1 {
margin: 20px;
background: red;
transform: translateX(-50px);
display: inline-block;
}
#c2 {
margin: 20px;
background: green;
transform: rotate(45deg);
display: inline-block;
}
#c3 {
margin: 20px;
background: blue;
display: inline-block;
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg);
display: inline-block;
}
<p>
foo
</p>
<div id="c1">
<div id="c2">
<div id="c3">
<canvas></canvas>
</div>
</div>
</div>
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
The code above works. Move the mouse over either of the yellow canvas elements and you'll see it draws correctly.
But, as soon as I add some 3D transform it fails.
Change the CSS for '#c6' to
#c6 {
background: pink;
transform: rotate(45deg) rotateX(45deg); /* changed */
display: inline-block;
}
and now when I draw over the right yellow canvas things are off.
const m4 = twgl.m4;
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', (e) => {
const pos = getElementRelativeMousePosition(e, canvas);
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(pos.x - 1, pos.y - 1, 3, 3);
});
});
function getElementRelativeMousePosition(e, elem) {
const pos = convertPointFromPageToNode(elem, e.pageX, e.pageY);
return {
x: pos[0],
y: pos[1],
};
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
function convertPointFromPageToNode(elem, pageX, pageY) {
const mat = m4.inverse(getTransformationMatrix(elem));
return m4.transformPoint(mat, [pageX, pageY, 0]);
};
function getTransformationMatrix(elem) {
let matrix = m4.identity();
let currentElem = elem;
while (currentElem !== undefined &&
currentElem !== currentElem.ownerDocument.documentElement) {
const style = window.getComputedStyle(currentElem);
const localMatrix = parseMatrix(style.transform);
matrix = m4.multiply(localMatrix, matrix);
currentElem = currentElem.parentElement;
}
const w = elem.offsetWidth;
const h = elem.offsetHeight;
let i = 4;
let left = +Infinity;
let top = +Infinity;
for (let i = 0; i < 4; ++i) {
const p = m4.transformPoint(matrix, [w * (i & 1), h * ((i & 2) >> 1), 0]);
left = Math.min(p[0], left);
top = Math.min(p[1], top);
}
const rect = elem.getBoundingClientRect()
document.querySelector('p').textContent =
`${w}x${h}`;
matrix = m4.multiply(m4.translation([
window.pageXOffset + rect.left - left,
window.pageYOffset + rect.top - top,
0]), matrix);
return matrix;
}
function parseMatrix(str) {
if (str.startsWith('matrix3d(')) {
return str.substring(9, str.length - 1).split(',').map(v => parseFloat(v.trim()));
} else if (str.startsWith('matrix(')) {
const m = str.substring(7, str.length - 1).split(',').map(v => parseFloat(v.trim()));
return [
m[0], m[1], 0, 0,
m[2], m[3], 0, 0,
0, 0, 1, 0,
m[4], m[5], 0, 1,
]
} else if (str == 'none') {
return m4.identity();
}
throw new Error('unknown format');
}
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c1 {
margin: 20px;
background: red;
transform: translateX(-50px);
display: inline-block;
}
#c2 {
margin: 20px;
background: green;
transform: rotate(45deg);
display: inline-block;
}
#c3 {
margin: 20px;
background: blue;
display: inline-block;
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg) rotateX(45deg);
display: inline-block;
}
<p>
foo
</p>
<div id="c1">
<div id="c2">
<div id="c3">
<canvas></canvas>
</div>
</div>
</div>
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Any ideas what I'm doing wrong?
Note: That's just a complementary answer on what OP already did find by themselves.
You can actually make this all work by using the MouseEvent constructor.
You can pass the clientX
and clientY
properties of this Event inside the constructor (or pageX
& pageY
if you prefer), then dispatching this composed event to your target will set its offsetX
and offsetY
properties relative to the target.
Since dispatchEvent does fire the Event synchronously, we can even make a converter:
const init_pos = { x: 50, y: 50};
const relative_pos = {};
const canvas = document.querySelector('canvas');
canvas.addEventListener('mousemove', e => {
relative_pos.x = e.offsetX;
relative_pos.y = e.offsetY;
}, {once: true});
canvas.dispatchEvent(new MouseEvent('mousemove', {
clientX: init_pos.x,
clientY: init_pos.y
}));
// synchronously log
console.log(relative_pos);
canvas {
display: block;
background: yellow;
transform: scale(0.75);
}
#c4 {
position: absolute;
top: 0;
background: cyan;
transform: translateX(-250px) rotate(55deg);
display: inline-block;
}
#c5 {
background: magenta;
transform: translate(50px);
display: inline-block;
}
#c6 {
background: pink;
transform: rotate(45deg);
display: inline-block;
}
<div id="c4">
<div id="c5">
<div id="c6">
<canvas></canvas>
</div>
</div>
</div>
Now, given the example in your own answer, you may want to actually hold a single object that will keep the global Event's position, and to get the relative positions of your canvas at every frame in a requestAnimationFrame
loop.
However, this setup will obviously traverse your canvases, if you want only the visible face to handle the events, then you'd have to check which one matches document.elementFromPoint(x, y)
, which itself needs your elements to react to pointer-events.
// will hold our last event's position
const pos = {
x: 0,
y: 0
};
const canvases = document.querySelectorAll('canvas');
// A single global "real" MouseEvent handler
document.body.onmousemove = (e) => {
pos.x = e.clientX;
pos.y = e.clientY;
};
canvases.forEach(canvas => {
const ctx = canvas.getContext('2d');
let count = 0;
canvas.addEventListener('mousemove', draw);
function draw(e) {
// do not fire on real Events
if (e.cancelable) return;
const x = e.offsetX * canvas.width / canvas.clientWidth;
const y = e.offsetY * canvas.height / canvas.clientHeight;
if (x < 0 || x > canvas.width || y < 0 || y > canvas.height) {
return;
}
ctx.fillStyle = hsl((count++ % 10) / 10, 1, 0.5);
ctx.fillRect(x - 1, y - 1, 3, 3);
}
});
anim();
function anim() {
requestAnimationFrame(anim);
// in case we want to paint only on the front element
const front_elem = single_face.checked && document.elementFromPoint(pos.x, pos.y);
// at every frame
canvases.forEach(c => {
if (!front_elem || c === front_elem) {
// force a composed event (synchronously, so we are still in rAF callback)
c.dispatchEvent(
new MouseEvent('mousemove', {
clientX: pos.x,
clientY: pos.y
})
);
}
});
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
.scene {
width: 200px;
height: 200px;
perspective: 600px;
}
.cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
animation-duration: 16s;
animation-name: rotate;
animation-iteration-count: infinite;
animation-timing-function: linear;
pointer-events: none; /* no need for mouse events */
}
#single_face:checked+.scene .cube {
pointer-events: all; /* except if we want to find out who is the front one */
}
label,#single_face {float: right}
@keyframes rotate {
from {
transform: translateZ(-100px) rotateX( 0deg) rotateY( 0deg);
}
to {
transform: translateZ(-100px) rotateX(360deg) rotateY(720deg);
}
}
.cube__face {
position: absolute;
width: 200px;
height: 200px;
display: block;
}
.cube__face--front {
background: rgba(255, 0, 0, 0.2);
transform: rotateY( 0deg) translateZ(100px);
}
.cube__face--right {
background: rgba(0, 255, 0, 0.2);
transform: rotateY( 90deg) translateZ(100px);
}
.cube__face--back {
background: rgba(0, 0, 255, 0.2);
transform: rotateY(180deg) translateZ(100px);
}
.cube__face--left {
background: rgba(255, 255, 0, 0.2);
transform: rotateY(-90deg) translateZ(100px);
}
.cube__face--top {
background: rgba(0, 255, 255, 0.2);
transform: rotateX( 90deg) translateZ(100px);
}
.cube__face--bottom {
background: rgba(255, 0, 255, 0.2);
transform: rotateX(-90deg) translateZ(100px);
}
<label>Draw on a single face</label><input type="checkbox" id="single_face">
<div class="scene">
<div class="cube">
<canvas class="cube__face cube__face--front"></canvas>
<canvas class="cube__face cube__face--back"></canvas>
<canvas class="cube__face cube__face--right"></canvas>
<canvas class="cube__face cube__face--left"></canvas>
<canvas class="cube__face cube__face--top"></canvas>
<canvas class="cube__face cube__face--bottom"></canvas>
</div>
</div>
<pre id="debug"></pre>
这篇关于如何获得CSS 3D转换画布的画布相对鼠标位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!