Jadi ini adalah topik yang sederhana namun sedikit lebih rumit dari yang terlihat.
Pertama, biasanya ada pertanyaan yang disatukan di sini
Cara mendapatkan koordinat relatif elemen mouse
Cara mendapatkan koordinat kanvas pixel mouse untuk 2D Canvas API atau WebGL
jadi, jawablah
Cara mendapatkan koordinat relatif elemen mouse
Apakah elemen adalah kanvas yang mendapatkan elemen koordinat mouse relatif sama untuk semua elemen.
Ada 2 jawaban sederhana untuk pertanyaan "Cara mendapatkan koordinat kanvas relatif mouse"
Jawaban sederhana penggunaan # 1 offsetX
danoffsetY
canvas.addEventListner('mousemove', (e) => {
const x = e.offsetX;
const y = e.offsetY;
});
Jawaban ini berfungsi di Chrome, Firefox, dan Safari. Tidak seperti semua nilai acara lainnya offsetX
dan offsetY
memperhitungkan transformasi CSS.
Masalah terbesar dengan offsetX
dan offsetY
pada 2019/05 mereka tidak ada pada acara sentuh sehingga tidak dapat digunakan dengan iOS Safari. Mereka memang ada di Acara Pointer yang ada di Chrome dan Firefox tetapi tidak Safari meskipun Safari tampaknya bekerja di sana .
Masalah lainnya adalah acara harus di kanvas itu sendiri. Jika Anda meletakkannya di beberapa elemen lain atau jendela, Anda tidak dapat kemudian memilih kanvas untuk menjadi referensi Anda.
Jawaban sederhana # 2 gunakan clientX
, clientY
dancanvas.getBoundingClientRect
Jika Anda tidak peduli dengan transformasi CSS, jawaban paling sederhana berikutnya adalah memanggil canvas. getBoundingClientRect()
dan mengurangi yang tersisa dari clientX
dan top
dari clientY
seperti pada
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
});
Ini akan berfungsi selama tidak ada transformasi CSS. Ini juga berfungsi dengan acara sentuh dan begitu juga bekerja dengan Safari iOS
canvas.addEventListener('touchmove', (e) => {
const rect = canvas. getBoundingClientRect();
const x = e.touches[0].clientX - rect.left;
const y = e.touches[0].clientY - rect.top;
});
Cara mendapatkan koordinat kanvas pixel mouse untuk 2D Canvas API
Untuk ini kita perlu mengambil nilai yang kita dapatkan di atas dan mengkonversi dari ukuran kanvas yang ditampilkan ke jumlah piksel di kanvas itu sendiri
dengan canvas.getBoundingClientRect
dan clientX
danclientY
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const elementRelativeX = e.clientX - rect.left;
const elementRelativeY = e.clientY - rect.top;
const canvasRelativeX = elementRelativeX * canvas.width / rect.width;
const canvasRelativeY = elementRelativeY * canvas.height / rect.height;
});
atau dengan offsetX
danoffsetY
canvas.addEventListener('mousemove', (e) => {
const elementRelativeX = e.offsetX;
const elementRelativeX = e.offsetY;
const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;
const canvasRelativeY = elementRelativeX * canvas.height / canvas.clientHeight;
});
Catatan: Dalam semua kasus, jangan menambahkan bantalan atau batas pada kanvas. Melakukan hal itu akan sangat menyulitkan kode. Alih-alih Anda ingin perbatasan atau bantalan mengelilingi kanvas di beberapa elemen lain dan menambahkan bantalan dan atau perbatasan ke elemen luar.
Contoh kerja menggunakan event.offsetX
,event.offsetY
[...document.querySelectorAll('canvas')].forEach((canvas) => {
const ctx = canvas.getContext('2d');
ctx.canvas.width = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let count = 0;
function draw(e, radius = 1) {
const pos = {
x: e.offsetX * canvas.width / canvas.clientWidth,
y: e.offsetY * canvas.height / canvas.clientHeight,
};
document.querySelector('#debug').textContent = count;
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
ctx.fill();
}
function preventDefault(e) {
e.preventDefault();
}
if (window.PointerEvent) {
canvas.addEventListener('pointermove', (e) => {
draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
});
canvas.addEventListener('touchstart', preventDefault, {passive: false});
canvas.addEventListener('touchmove', preventDefault, {passive: false});
} else {
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mousedown', preventDefault);
}
});
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;
}
@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); }
<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>
Contoh kerja menggunakan canvas.getBoundingClientRect
dan event.clientX
danevent.clientY
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let count = 0;
function draw(e, radius = 1) {
const rect = canvas.getBoundingClientRect();
const pos = {
x: (e.clientX - rect.left) * canvas.width / canvas.clientWidth,
y: (e.clientY - rect.top) * canvas.height / canvas.clientHeight,
};
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2);
ctx.fillStyle = hsl((count++ % 100) / 100, 1, 0.5);
ctx.fill();
}
function preventDefault(e) {
e.preventDefault();
}
if (window.PointerEvent) {
canvas.addEventListener('pointermove', (e) => {
draw(e, Math.max(Math.max(e.width, e.height) / 2, 1));
});
canvas.addEventListener('touchstart', preventDefault, {passive: false});
canvas.addEventListener('touchmove', preventDefault, {passive: false});
} else {
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mousedown', preventDefault);
}
function hsl(h, s, l) {
return `hsl(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%)`;
}
canvas { background: #FED; }
<canvas width="400" height="100" style="width: 300px; height: 200px"></canvas>
<div>canvas deliberately has differnt CSS size vs drawingbuffer size</div>