[英]Javascript Canvas Relative Mouse Coordinates on Rotated Rectangle?
[英]Determine if mouse was clicked on a rotated rectangle on a canvas
我正在使用以下鏈接中發布的示例:
我正在嘗試使用這個示例,對 canvas 上的 2 個矩形進行一些修改。有幾個問題(它們可能是相關的),但讓我從一個開始:
鼠標點擊不再有效。
我希望有人能幫助我指出需要什么補救措施,因為我為此苦苦掙扎了一天多,顯然我對 Javascrip 中的對象和類並不了解。 代碼在這里: https://jsfiddle.net/jackmstein/ngyfrcms/2/
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
// Transformation Matrix "Class"
var TransformationMatrix=( function(){
// private
var self;
var m=[1,0,0,1,0,0];
var reset=function(){ var m=[1,0,0,1,0,0]; }
var multiply=function(mat){
var m0=m[0]*mat[0]+m[2]*mat[1];
var m1=m[1]*mat[0]+m[3]*mat[1];
var m2=m[0]*mat[2]+m[2]*mat[3];
var m3=m[1]*mat[2]+m[3]*mat[3];
var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];
var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];
m=[m0,m1,m2,m3,m4,m5];
}
var screenPoint=function(transformedX,transformedY){
// invert
var d =1/(m[0]*m[3]-m[1]*m[2]);
im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];
// point
return({
x:transformedX*im[0]+transformedY*im[2]+im[4],
y:transformedX*im[1]+transformedY*im[3]+im[5]
});
}
var transformedPoint=function(screenX,screenY){
return({
x:screenX*m[0] + screenY*m[2] + m[4],
y:screenX*m[1] + screenY*m[3] + m[5]
});
}
// public
function TransformationMatrix(){
self=this;
}
// shared methods
TransformationMatrix.prototype.translate=function(x,y){
var mat=[ 1, 0, 0, 1, x, y ];
multiply(mat);
};
TransformationMatrix.prototype.rotate=function(rAngle){
var c = Math.cos(rAngle);
var s = Math.sin(rAngle);
var mat=[ c, s, -s, c, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.scale=function(x,y){
var mat=[ x, 0, 0, y, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.skew=function(radianX,radianY){
var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.reset=function(){
reset();
}
TransformationMatrix.prototype.setContextTransform=function(ctx){
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
}
TransformationMatrix.prototype.resetContextTransform=function(ctx){
ctx.setTransform(1,0,0,1,0,0);
}
TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){
return(transformedPoint(screenX,screenY));
}
TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){
return(screenPoint(transformedX,transformedY));
}
TransformationMatrix.prototype.getMatrix=function(){
var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];
return(clone);
}
// return public
return(TransformationMatrix);
})();
// DEMO starts here
// create a rect and add a transformation matrix
// to track it's translations, rotations & scalings
var rect1={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};
// draw the untransformed rect in black
// ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
// Demo: label
// ctx.font='11px arial';
// ctx.fillText('Untransformed Rect',rect.x,rect.y-10);
// transform the canvas & draw the transformed rect in red
ctx.translate(100,0);
ctx.scale(2,2);
ctx.rotate(Math.PI/12);
// draw the transformed rect
ctx.strokeStyle='red';
ctx.strokeRect(rect1.x, rect1.y, rect1.w, rect1.h);
ctx.font='6px arial';
// Demo: label
ctx.fillText('Rect: Translated, rotated & scaled',rect1.x,rect1.y-6);
// reset the context to untransformed state
ctx.setTransform(1,0,0,1,0,0);
// record the transformations in the matrix
var m=rect1.matrix;
m.translate(100,0);
m.scale(2,2);
m.rotate(Math.PI/12);
// use the rect's saved transformation matrix to reposition,
// resize & redraw the rect
ctx.strokeStyle='blue';
drawTransformedRect(rect1);
// Demo: instructions
ctx.font='14px arial';
ctx.fillText('Demo: click inside the blue rect',30,200);
// DEMO starts here
// create a rect and add a transformation matrix
// to track it's translations, rotations & scalings
var rect2={x:150,y:30,w:50,h:35,matrix:new TransformationMatrix()};
// draw the untransformed rect in black
// ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
// Demo: label
// ctx.font='11px arial';
// ctx.fillText('Untransformed Rect',rect.x,rect.y-10);
// transform the canvas & draw the transformed rect in red
ctx.translate(100,0);
ctx.scale(2,2);
ctx.rotate(Math.PI/12);
// draw the transformed rect
ctx.strokeStyle='red';
ctx.strokeRect(rect2.x, rect2.y, rect2.w, rect2.h);
ctx.font='6px arial';
// Demo: label
ctx.fillText('Rect: Translated, rotated & scaled',rect2.x,rect2.y-6);
// reset the context to untransformed state
ctx.setTransform(1,0,0,1,0,0);
// record the transformations in the matrix
var m=rect2.matrix;
m.translate(100,0);
m.scale(2,2);
m.rotate(Math.PI/12);
// use the rect's saved transformation matrix to reposition,
// resize & redraw the rect
ctx.strokeStyle='blue';
drawTransformedRect(rect2);
// Demo: instructions
// redraw a rect based on it's saved transformation matrix
function drawTransformedRect(r){
// set the context transformation matrix using the rect's saved matrix
m.setContextTransform(ctx);
// draw the rect (no position or size changes needed!)
ctx.rect( r.x, r.y, r.w, r.h );
// reset the context transformation to default (==untransformed);
m.resetContextTransform(ctx);
}
// is the point in the transformed rectangle?
function isPointInTransformedRect(r,transformedX,transformedY){
var p=r.matrix.getScreenPoint(transformedX,transformedY);
var x=p.x;
var y=p.y;
return(x>r.x && x<r.x+r.w && y>r.y && y<r.y+r.h);
}
// listen for mousedown events
canvas.onmousedown=handleMouseDown;
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// is the mouse inside the transformed rect?
var rect1={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};
if(isPointInTransformedRect(rect1,mouseX,mouseY)){
alert('You clicked in the transformed Rect 1');
}
var rect2={x:150,y:30,w:50,h:35,matrix:new TransformationMatrix()};
if(isPointInTransformedRect(rect2,mouseX,mouseY)){
alert('You clicked in the transformed Rect 2');
}
}
// Demo: redraw transformed rect without using
// context transformation commands
function drawTransformedRect(r,color){
var m=r.matrix;
var tl=m.getTransformedPoint(r.x,r.y);
var tr=m.getTransformedPoint(r.x+r.w,r.y);
var br=m.getTransformedPoint(r.x+r.w,r.y+r.h);
var bl=m.getTransformedPoint(r.x,r.y+r.h);
ctx.beginPath();
ctx.moveTo(tl.x,tl.y);
ctx.lineTo(tr.x,tr.y);
ctx.lineTo(br.x,br.y);
ctx.lineTo(bl.x,bl.y);
ctx.closePath();
ctx.strokeStyle=color;
ctx.stroke();
}
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=600 height=250></canvas>
</body>
</html>
注意這個答案使用統一變換。 (傾斜不起作用,x 和 y 軸必須具有相同的刻度);
為了盡可能簡單,矩形(框)由其中心pos
及其寬度和高度size
定義。
const Point = (x = 0, y = 0) => ({x, y});
const Size = (w = 0, h = 0) => ({w, h});
const Rect = (pos, size) = ({pos, size});
我們可以通過根據需要創建變換來創建旋轉(變換)的rect
。 (比創建DOMMatrix
或自定義矩陣更快)
function pathRect(rect, ang, scale) { // ang in radians
const xax = Math.cos(ang) * scale;
const xay = Math.sin(ang) * scale;
ctx.setTransform(xax, xay, -xay, xax, rect.pos.x, rect.pos.y);
ctx.rect(-rect.size.w * 0.5, -rect.size.h * 0.5, rect.size.w, rect.size.h);
}
為了找到相對於rect
的點,我們應用應用於rect
的變換的逆(反向)。
relPoint
轉換為框相對單位值。 也就是說,如果該點位於(旋轉矩形的)左上角,則relPoint
將為 {x:0, y:0},位於中心 {x: 0.5, y: 0.5},右下角為 {x: 1, y :1}。 這簡化了邊界測試(見演示)
function pointRelative2Rect(point, relPoint, rect, ang, scale) {
const xax = Math.cos(-ang) / scale;
const xay = Math.sin(-ang) / scale;
const x = point.x - rect.pos.x;
const y = point.y - rect.pos.y;
relPoint.x = (xax * x - xay * y) / rect.size.w + 0.5;
relPoint.y = (xay * x + xax * y) / rect.size.h + 0.5;
}
因此,如果我們有一個mouse
位置,那么我們可以找到它何時在rect
上使用
pointRelative2Rect(mouse, mouseRel, rect, rot, scale);
if (mouseRel.x < 0 || mouseRel.x > 1 || mouseRel.y < 0 || mouseRel.y > 1) {
// mouse outside rect
} else {
// mouse over rect
}
演示創建一個隨機矩形,該矩形動畫隨時間旋轉和縮放。 它使用上面概述的方法來測試鼠標是否在rect
內。 當鼠標懸停時,該框將變為紅色。
const ctx = canvas.getContext("2d"); const w = 256, h = 256; const Point = (x = 0, y = 0) => ({x, y}); const Size = (w = 0, h = 0) => ({w, h}); const Rect = (pos, size) => ({pos, size}); const randI = (min, max) => Math.random() * (max - min) + min; const mouse = Point(), mouseRel = Point(); document.addEventListener("mousemove", e => { mouse.x = e.pageX; mouse.y = e.pageY; }); const randRect = () => Rect( Point(randI(40, 210), randI(40, 210)), Size(randI(10, 30), randI(10, 30)) ); const rect = randRect(); function pathRect(rect, ang, scale) { // ang in radians const xax = Math.cos(ang) * scale; const xay = Math.sin(ang) * scale; ctx.setTransform(xax, xay, -xay, xax, rect.pos.x, rect.pos.y); ctx.rect(-rect.size.w * 0.5, -rect.size.h * 0.5, rect.size.w, rect.size.h); } function pointRelative2Rect(point, resPoint, rect, ang, scale) { const xax = Math.cos(-ang) / scale; const xay = Math.sin(-ang) / scale; const x = point.x - rect.pos.x; const y = point.y - rect.pos.y; resPoint.x = (xax * x - xay * y) / rect.size.w + 0.5; resPoint.y = (xay * x + xax * y) / rect.size.h + 0.5; } requestAnimationFrame(renderLoop); function renderLoop(time) { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0, 0, w, h); const rot = time / 3000; const scale = Math.sin(time / 2777) * 0.5 + 2.1; ctx.beginPath(); pointRelative2Rect(mouse, mouseRel, rect, rot, scale); if (mouseRel.x < 0 || mouseRel.x > 1 || mouseRel.y < 0 || mouseRel.y > 1) { canvas.style.cursor = "default"; ctx.strokeStyle = "#000"; } else { canvas.style.cursor = "pointer"; ctx.strokeStyle = "#F00"; } pathRect(rect, rot, scale); ctx.lineWidth = 1 / scale; ctx.stroke(); requestAnimationFrame(renderLoop); }
canvas { position: absolute; top: 0px; left: 0px; }
<canvas id="canvas" width="256" height="256"><canvas>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.