Javascript使图像旋转,总是看鼠标光标?

时间:2016-10-19 01:07:45

标签: javascript jquery css canvas html5-canvas

我试图在javascript中使用箭头指向我的鼠标光标。现在它只是猛烈旋转,而不是指向光标。

以下是我的代码的小提琴:https://jsfiddle.net/pk1w095s/

以下是自己的代码:

var cv = document.createElement('canvas');
cv.width = 1224;
cv.height = 768;
document.body.appendChild(cv);

var rotA = 0;

var ctx = cv.getContext('2d');

var arrow = new Image();
var cache;
arrow.onload = function() {
    cache = this;
    ctx.drawImage(arrow, cache.width/2, cache.height/2);
};

arrow.src = 'https://d30y9cdsu7xlg0.cloudfront.net/png/35-200.png';

var cursorX;
var cursorY;
document.onmousemove = function(e) {
    cursorX = e.pageX;
    cursorY = e.pageY;

    ctx.save(); //saves the state of canvas
    ctx.clearRect(0, 0, cv.width, cv.height); //clear the canvas
    ctx.translate(cache.width, cache.height); //let's translate


    var centerX = cache.x + cache.width / 2;
    var centerY = cache.y + cache.height / 2;



    var angle = Math.atan2(e.pageX - centerX, -(e.pageY - centerY)) * (180 / Math.PI);
    ctx.rotate(angle);

    ctx.drawImage(arrow, -cache.width / 2, -cache.height / 2, cache.width, cache.height); //draw the image
    ctx.restore(); //restore the state of canvas
};

2 个答案:

答案 0 :(得分:7)

在第一个例子中,摆脱转换为度数 - Math.atan2ctx.rotate函数都采用弧度。

修复了疯狂旋转 - 你仍然有一些数学错误,最容易通过从数学中分割出绘图来整理出来。

下面的函数绘制旋转给定角度的箭头:

// NB: canvas rotations go clockwise
function drawArrow(angle) {
    ctx.clearRect(0, 0, cv.width, cv.height);
    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.rotate(-Math.PI / 2);  // correction for image starting position
    ctx.rotate(angle);
    ctx.drawImage(arrow, -arrow.width / 2, -arrow.height / 2);
    ctx.restore();
}

并且onmove处理程序只是指出方向。

document.onmousemove = function(e) {
    var dx = e.pageX - centerX;
    var dy = e.pageY - centerY;
    var theta = Math.atan2(dy, dx);
    drawArrow(theta);
};

请注意,在画布上,Y轴指向下方(与正常的笛卡尔坐标相反),因此旋转最终顺时针方向而不是逆时针方向。

https://jsfiddle.net/alnitak/5vp0syn5/

的工作演示

答案 1 :(得分:3)

“最佳实践”解决方案。

作为,现有的(Alnitak's)答案存在一些问题。

  • 错误登录计算,然后进行太多调整以纠正错误的符号。
  • 箭头未指向鼠标,因为鼠标坐标不正确。尝试将鼠标移动到箭头的尖端(接受(Alnitak's)答案),你可以看到它只能在画布上的两个点上运行。需要针对画布填充/偏移
  • 更正鼠标
  • 画布坐标需要包含页面滚动位置,因为鼠标事件pageXpageY属性相对于页面的左上角而不是整个文档。如果滚动页面,则箭头将不再指向鼠标。或者您可以使用鼠标事件clientX,clientY属性将鼠标坐标保持在客户端(整个)页面左上方,因此您无需更正滚动。
  • 使用保存和恢复效率很低。使用setTransform
  • 不需要时渲染。鼠标比屏幕刷新时间多了很多。鼠标触发时的渲染将只会导致从未见过的渲染。渲染在处理和电源使用方面都很昂贵。不必要的渲染会很快耗尽设备的电池

这是一个“最佳实践”解决方案。

核心功能绘制图像,查看点lookxlooky

var drawImageLookat(img, x, y, lookx, looky){
   ctx.setTransform(1, 0, 0, 1, x, y);  // set scale and origin
   ctx.rotate(Math.atan2(looky - y, lookx - x)); // set angle
   ctx.drawImage(img,-img.width / 2, -img.height / 2); // draw image
   ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}

该演示展示如何使用requestAnimationFrame确保仅在DOM准备好渲染时进行渲染,使用getBoundingClientRect获取相对于画布的鼠标位置。

左上方的计数器显示已触发的不需要渲染的鼠标事件数。非常慢地移动鼠标,计数器不会增加。以正常速度移动鼠标,您将看到每隔几秒就可以生成100个不需要的渲染事件。第二个数字是以1/1000秒保存的大致时间,%是随时间保存的比率时间。

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 512;
canvas.height = 512;
canvas.style.border = "1px solid black";
document.body.appendChild(canvas);
var renderSaveCount = 0; // Counts the number of mouse events that we did not have to render the whole scene

var arrow = {
    x : 256,
    y : 156,
    image : new Image()
};
var mouse = {
    x : null,
    y : null,
    changed : false,
    changeCount : 0,
}


arrow.image.src = 'https://d30y9cdsu7xlg0.cloudfront.net/png/35-200.png';

function drawImageLookat(img, x, y, lookx, looky){
     ctx.setTransform(1, 0, 0, 1, x, y);
     ctx.rotate(Math.atan2(looky - y, lookx - x) - Math.PI / 2); // Adjust image 90 degree anti clockwise (PI/2) because the image  is pointing in the wrong direction.
     ctx.drawImage(img, -img.width / 2, -img.height / 2);
     ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default not needed if you use setTransform for other rendering operations
}
function drawCrossHair(x,y,color){
    ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(x - 10, y);
    ctx.lineTo(x + 10, y);
    ctx.moveTo(x, y - 10);
    ctx.lineTo(x, y + 10);
    ctx.stroke();
}

function mouseEvent(e) {  // get the mouse coordinates relative to the canvas top left
    var bounds = canvas.getBoundingClientRect(); 
    mouse.x = e.pageX - bounds.left;
    mouse.y = e.pageY - bounds.top;
    mouse.cx = e.clientX - bounds.left; // to compare the difference between client and page coordinates
    mouse.cy = e.clienY - bounds.top;
    mouse.changed = true;
    mouse.changeCount += 1;
}
document.addEventListener("mousemove",mouseEvent);
var renderTimeTotal = 0;
var renderCount = 0;
ctx.font = "18px arial";
ctx.lineWidth = 1;
// only render when the DOM is ready to display the mouse position
function update(){
    if(arrow.image.complete && mouse.changed){ // only render when image ready and mouse moved
        var now = performance.now();
        mouse.changed = false; // flag that the mouse coords have been rendered
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        // get mouse canvas coordinate correcting for page scroll
        var x = mouse.x - scrollX;
        var y = mouse.y - scrollY;
        drawImageLookat(arrow.image, arrow.x, arrow.y, x ,y);
        // Draw mouse at its canvas position
        drawCrossHair(x,y,"black");
        // draw mouse event client coordinates on canvas
        drawCrossHair(mouse.cx,mouse.cy,"rgba(255,100,100,0.2)");
       
        // draw line from arrow center to mouse to check alignment is perfect
        ctx.strokeStyle = "black";
        ctx.beginPath();
        ctx.globalAlpha = 0.2;
        ctx.moveTo(arrow.x, arrow.y);
        ctx.lineTo(x, y);
        ctx.stroke();
        ctx.globalAlpha = 1;

        // Display how many renders that were not drawn and approx how much time saved (excludes DOM time to present canvas to display)
        renderSaveCount += mouse.changeCount -1;
        mouse.changeCount = 0;
        var timeSaved = ((renderTimeTotal / renderCount) * renderSaveCount);
        var timeRatio = ((timeSaved / renderTimeTotal) * 100).toFixed(0);

        ctx.fillText("Avoided "+ renderSaveCount + " needless renders. Saving ~" + timeSaved.toFixed(0) +"ms " + timeRatio + "% .",10,20);
        // get approx render time per frame
        renderTimeTotal += performance.now()-now;
        renderCount += 1;

    }
    requestAnimationFrame(update);

}
requestAnimationFrame(update);