- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在实现一个具有类似于 Google map 的点击并拖动和缩放功能的应用程序。我已经设法实现了平移和缩放,但是缩放点当前位于坐标 0,0
。当您放大和缩小时,坐标 0,0
处的网格位置保持固定,而所有其他坐标从该点拉近/拉远。
相反,我希望能够实现稳定的缩放,其中缩放点是当前鼠标位置下的位置。要了解我在寻找什么,请打开 Google map ,将鼠标放在特定点上方并使用鼠标滚轮滚动。请注意鼠标下方的位置是如何保持固定的。
如何修改此示例中的 zoomGrid
函数以实现符合鼠标位置的鼠标滚轮缩放?
function zoomGrid(mouseEvent) {
var delta = mouseEvent.deltaY;
if (mouseEvent.deltaMode == 1) { //Firefox scrolls by line instead of by pixel so multiply the delta by 20
delta *= 20;
}
zoom += delta;
zoom = Math.min(zoom, 3000);
zoom = Math.max(zoom, -1000);
scale = Math.pow(2,(zoom / 1000));
var mousePos = {x: mouseEvent.offsetX, y: mouseEvent.offsetY};
//gridPos = ???
drawGrid();
drawShapes();
}
完整演示:http://codepen.io/alexspurling/pen/jApazY
(PS 我已经看到了 paper.js tutorial 但无法将其中的逻辑转换为可用的代码)。
最佳答案
这里有一些代码可以做你想做的事..
这个答案有点过头了,但我没有时间剪掉不需要的东西。
该演示可让您缩放、平移和旋转。它首先加载图像(大小合理,因此具有低 GPU RAM 的设备可能不喜欢它并且运行缓慢)。
汽车图片加载完成后。
只使用绝对坐标渲染。如果您有一个大型数据集,您可以在剔除渲染调用时使用显示变换 Angular 作为世界坐标 View 边界..
重要的功能在顶部。
display
每帧调用一次并处理所有渲染displayTransform
是负责平移、缩放和旋转的对象。它有一些评论,但没有解释,所以如果您遇到问题,请随时提问onResize
在 debounced resize 事件上调用startup
在启动时调用一次并设置 displayTransformmouse
是一个保存鼠标状态的全局对象。mouse.x
, mouse.y
是 Canvas 坐标mouse.buttonRaw
是位字段,如果按下按钮,位将打开。见下一段mouse.w
是滚轮 -120、0 或 120
displayTransform.mouseWorldX
和 displayTransform.mouseWorldY
保存鼠标在图像上的位置(因为这与 Canvas 坐标不匹配);displayTransform.corners
是一个长度为 8 的数组, Canvas Angular 的坐标为 [x1,y1,...,x4,y4] 从左上角开始顺时针方向投影到世界(图像)坐标。您将使用它们绘制网格并剔除 View 外的渲染调用。
我没有添加任何鼠标按钮常量,所以当您看到任何与鼠标按钮有关的内容时,0 是左 1 是中间 2 是右作为 ID。当检查鼠标点击时,mouse.buttonRaw
是一个位域,第 1 位左边,中间 2 位,右边 3 位。仅屏蔽您感兴趣的按钮 mouse.buttonRaw & 1在左边
&2中间和
&4`在右边。
var startup = function(){
displayTransform.ctx = ctx;
displayTransform.mouse = mouse;
displayTransform.setMouseRotate(2); // set rotate funtion to button 3
displayTransform.setMouseTranslate(0);
displayTransform.setWheelZoom();
img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/e/e5/Fiat_500_in_Emilia-Romagna.jpg"
}
var img;
var onResize = function(){
ctx.font = "14px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
if(img.complete){
displayTransform.fitView(0,0,img.width,img.height,"fit");
}
}
var stillTime = 0;
const MOUSE_STILL_TIME = 1000;
function display(){
displayTransform.update();// update the transform
displayTransform.setDefault();// set home transform to clear the screem
ctx.clearRect(0,0,canvas.width,canvas.height);
// if the image loaded show it
if(img.complete){
if(displayTransform.quiet || (mouse.buttonRaw & 2)){
stillTime += 1;
if(stillTime > MOUSE_STILL_TIME || (mouse.buttonRaw & 2)){
stillTime = 0;
displayTransform.fitView(0,0,img.width,img.height,"fit")
}
}else{
stillTime = 0;
}
displayTransform.setTransform();
ctx.drawImage(img,0,0);
}else{
// waiting for image to load
displayTransform.setTransform();
ctx.fillText("Loading image...",100,100);
}
}
var displayTransform = (function(){
const buttons = [1, 2, 4];
// create a location description.
// x and y is the position of the (where on the canvas the transformed point 0,0 will end up)
// origin x,y is the location that zooms, rotations will be centered on.
// scale is the scale (zoom) large numbers are zooming in small is zoom out. 1 is 1pixel = 1pixel
// rotation is rotation. 0 id From left to right across the screen with positives values rotation
// clockwise. Values are in radians
var location = function (x, y, originX, originY, scale, rotation){
return {
x : x,
y : y,
ox : originX,
oy : originY,
scale : scale,
rotate : rotation,
};
}
// returns an array to hold the transformation matrix
// if a is undefined then returns the Identity (default) matrix
var matrix = function (a, b, c, d, e, f){
if(a === undefined){
return [1, 0, 0, 1, 0, 0];
}
return [a, b, c, d, e, f];
}
// set the ctx transformation
var setTransform = function(){
var m, i;
m = this.matrix;
i = 0;
this.ctx.setTransform(m[i ++], m[i ++], m[i ++], m[i ++], m[i ++], m[i ++]);
}
// uses chase values to smooth out transformations and then sets the matrix and invMatrix
// The inverMatrix is used to transform a point from world space to screen space.
var smoothTransform = function(){
var a, g, d, c, l, cross, m, im;
// create short vars for code clarity
a = this.acceleration;
g = this.drag;
l = this.location;
c = this.locationChaser;
d = this.locationDelta;
m = this.matrix;
im = this.invMatrix;
// update the chasing value. Explination of code below
// d += (l - c) * a; // accelerate the delta
// d *= g; // apply the drag
// c += d; // add the new delta to the chasing value
c.x += (d.x = (d.x += (l.x - c.x ) * a ) * g);
c.y += (d.y = (d.y += (l.y - c.y ) * a ) * g);
c.ox += (d.ox = (d.ox += (l.ox - c.ox ) * a ) * g);
c.oy += (d.oy = (d.oy += (l.oy - c.oy ) * a ) * g);
c.scale += (d.scale = (d.scale += (l.scale - c.scale ) * a ) * g);
c.rotate += (d.rotate = (d.rotate += (l.rotate - c.rotate ) * a ) * g);
// use x and y movement to determin if the display has reached its position
this.quiet = false;
if(Math.abs(c.x - l.x) < 0.1 && Math.abs(c.y - l.y) < 0.1 && Math.abs(c.rotate - l.rotate) < 0.001 ){
if(Math.abs(d.x) < 0.1 && Math.abs(d.y) < 0.1 && Math.abs(d.rotate) < 0.001){
this.quiet = true;
}
}
// calculate the matrix which is two vectors representing the X and Y axis
// the Y axis is 90Deg counter clockwise from the X
// To rotate a vector (v1) 90deg to (v2)
// v2.x = -v1.y;
// v2.y = v1.x;
// m[0],m[1] is the X axies vector and m[2],m[3] is the Y axis vector
m[3] = m[0] = Math.cos(c.rotate) * c.scale;
m[2] = -(m[1] = Math.sin(c.rotate) * c.scale);
// transform the x,y position around the origin and add to the matrix
m[4] = -(c.x * m[0] + c.y * m[2]) + c.ox;
m[5] = -(c.x * m[1] + c.y * m[3]) + c.oy;
// caculate the invers transformation
// first get the cross product of x axis and y axis
cross = m[0] * m[3] - m[1] * m[2];
// now get the inverted axies
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
im[4] = (m[1] * m[5] - m[3] * m[4]) / cross;
im[5] = (m[2] * m[4] - m[0] * m[5]) / cross;
// all done for mow
}
// Activates mouse translate on button mouseButton 0 = main (left click) 1 = middle 2 = right
var setUpMouseTranslate = function(mouseButton){
this.mouseAction[mouseButton] = this.mouseTranslate.bind(this);
this.mouseActionOff[mouseButton] = undefined;
}
// Does mouse drag translation
var mouseTranslate = function (mouse) {
var mdx, mdy;
// get the mouse delta
var mdx = mouse.x - this.mouseLastX; // get the mouse movement
var mdy = mouse.y - this.mouseLastY; // get the mouse movement
// Transform the mouse delta to world space and move the
// world position
this.location.x -= (mdx * this.invMatrix[0] + mdy * this.invMatrix[2]);
this.location.y -= (mdx * this.invMatrix[1] + mdy * this.invMatrix[3]);
}
// Set up mouse rotation on mouseButton 0 = main (left click) 1 = middle 2 = right
// User clicks and drags. When a distance 14 pixels is reached the angle from the
// start to that positoin is the referance. The user then drags around the
// start point to rotate the world
var setUpMouseRotate = function(mouseButton){
// extra data needed to do the rotation
this.rotationData = {
rotateStart : false, // the rotation has just started
rotateOX : 0, // the screen start location of the rottae
rotateOY : 0,
startAng : undefined, // the starting world rotatoin
lastAng : undefined, // last angle input. Used to track cyclic rotation
rotFrom : undefined, // the starting draged angle.
}
this.mouseAction[mouseButton] = this.mouseRotate.bind(this);
this.mouseActionOff[mouseButton] = (function(){
this.rotationData.rotateStart = true;
}).bind(this);
}
// Does the mouse drag rotation
var mouseRotate = function (mouse) {
var loc, mbx, mby, dist, rot, rd;
loc = this.location;
rd = this.rotationData;
// is this the start of a rotation gesture
// set the start location and the current rotation
if(rd.rotateStart){
rd.rotateStart = false;
rd.rotateOX = mouse.x;
rd.rotateOY = mouse.y;
loc.ox = mouse.x;
loc.oy = mouse.y;
loc.x = this.mouseWorldX;
loc.y = this.mouseWorldY;
rd.startAng = loc.rotate;
rd.lastAng = undefined;
rd.rotFrom = undefined;
}
// get mouse movement since start
mdx = mouse.x - rd.rotateOX;
mdy = mouse.y - rd.rotateOY;
dist = Math.hypot(mdy, mdx);
if(dist > 14){ // tollerance (too close and the rotation goes all over thr plavce)
rot = Math.atan2(mdy, mdx); // get the angle from the start of the geusture to the mouse
if(rd.lastAng === undefined){ // if the last ang is not avalible us the current angle
rd.lastAng = rot;
rd.rotFrom = rot;
}
// need to compensate for where atan2 goes from -Math.PI to Math.PI
// adds 360 or subtracts 360 depending on which way around the user is draggin the mouse
// can fail but I have been using this method for over 5 years
// and have never had a problem
if(rd.lastAng < -Math.PI / 2 && rot > Math.PI / 2 ){
rd.startAng -= Math.PI * 2;
}
if(rd.lastAng > Math.PI / 2 && rot < -Math.PI / 2 ){
rd.startAng += Math.PI * 2
}
loc.rotate = (rot-rd.rotFrom) + rd.startAng;
rd.lastAng = rot;
}
}
// turns on wheel zoom
var setWheelZoom = function(){
this.mouseWheel = this.mouseWheelZoom;
}
// does wheel zoom
var mouseWheelZoom = function (mouse) {
var loc;
loc = this.location;
loc.ox = mouse.x;
loc.oy = mouse.y;
loc.x = this.mouseWorldX;
loc.y = this.mouseWorldY;
if(mouse.w > 0){ // zoom in
loc.scale *= this.scaleSpeed;
mouse.w -= 20;
if(mouse.w < 0){
mouse.w = 0;
}
}
if(mouse.w < 0){ // zoom out
loc.scale *= this.invScaleSpeed;
mouse.w += 20;
if(mouse.w > 0){
mouse.w = 0;
}
}
}
// fits a location bound by x1,y1 and x2,y2 to fit within the
// canvas display
// type "fit" will ensure that all the area is displayed. There my be gaps
// above and below or left and right
// "fill" will ensure that the area fills the canva. there may be some
// cliping to the sides of top. The image will be centered
var setLocation = function (x1, y1, x2, y2, type){
var w,h, vw, vh, loc;
loc = this.location;
w = this.ctx.canvas.width;
h = this.ctx.canvas.height;
loc.ox = w/2;
loc.oy = h/2;
vw = x2 - x1;
vh = y2 - y1;
if(type === "fit"){
loc.scale = Math.min( w / vw, h / vh);
}else{
loc.scale = Math.max( w / vw, h / vh);
}
loc.x = (x1 + x2) / 2;// - (1 / loc.scale) * (w / 2);
loc.y = (y1 + y2) / 2;// - (1 / loc.scale) * (h / 2);
loc.rotate = Math.round(loc.rotate / (Math.PI * 2)) * Math.PI * 2;
}
// fits a location defined by x,y center and dx,dy the direction anddistance
// to the right side
var setOrientation = function (x, y, dx, dy){
var w,h, vx, vy, loc, ang, size;
loc = this.location;
w = this.ctx.canvas.width;
h = this.ctx.canvas.height;
loc.ox = w/2;
loc.oy = h/2;
vx = dx - x;
vy = dy - y;
loc.rotate =- Math.atan2(vy, vx);
size = Math.hypot(vx ,vy);
loc.scale = w / (size*2);
vx /= (size*2);
vy /= (size*2);
w = (1/loc.scale) * (w );
h = (1/loc.scale) * (h );
loc.x = x;// - w * vx - h * -vy;
loc.y = y;// - w * vy - h * vx;
}
// update transformation should be called once per frame
// Smooths and sets the transform on the current context (ctx).
// if There is a mouse avalilble then get the mouse world position
// and apply mouse gestures to update the world space.
var updateWorld = function () {
var msx, msy, im, m, loc, mouse, but, i, im0, im1, im2, im3, im4, im5, cor;
but = buttons;
m = this.matrix;
im = this.invMatrix;
loc = this.locationChaser;
cor = this.corners;
this.transform(); // update and set matrix
im0 = im[0];
im1 = im[1];
im2 = im[2];
im3 = im[3];
im4 = m[4];
im5 = m[5];
if(this.mouse !== undefined){
mouse = this.mouse;
// caculate the mouse world coordinates
msx = mouse.x - im4;
msy = mouse.y - im5;
this.mouseWorldX = (msx * im0 + msy * im2);
this.mouseWorldY = (msx * im1 + msy * im3);
i = 0;
// do any mouse actions
while( i < 3){
if(this.mouseAction[i] !== undefined){
if((mouse.buttonRaw & but[i]) === but[i]){
this.mouseAction[i](mouse);
}else
if(this.mouseActionOff[i] !== undefined){
this.mouseActionOff[i](mouse);
}
}
i++;
}
if(this.mouseWheel !== undefined){
if(mouse.w !== 0){
this.mouseWheel(mouse);
}
}
// caculate the mouse world coordinates
msx = mouse.x - im4;
msy = mouse.y - im5;
this.mouseWorldX = (msx * im0 + msy * im2);
this.mouseWorldY = (msx * im1 + msy * im3);
// save old mouse position as the mouse events may occure more
// offtent than the frame update. As we need the last position
// we used we stash the values here
this.mouseLastX = mouse.x;
this.mouseLastY = mouse.y;
}
msx = -im4;
msy = -im5;
cor[0] = (msx * im0 + msy * im2);
cor[1] = (msx * im1 + msy * im3);
msx = this.ctx.canvas.width - im4;
msy = this.ctx.canvas.height - im5;
cor[4] = (msx * im0 + msy * im2);
cor[5] = (msx * im1 + msy * im3);
msx = - im4;
msy = this.ctx.canvas.height - im5;
cor[6] = (msx * im0 + msy * im2);
cor[7] = (msx * im1 + msy * im3);
msx = this.ctx.canvas.width - im4;
msy = - im5;
cor[2] = (msx * im0 + msy * im2);
cor[3] = (msx * im1 + msy * im3);
this.invScale = 1/loc.scale;
this.pixelXx = im0;
this.pixelXy = im1;
}
// terms.
// Real space, real, r (prefix) refers to the transformed canvas space.
// c (prefix), chase is the value that chases a requiered value
var displayTransform = {
mode : "smooth",
location : location(0, 0, 0, 0, 1, 0),
locationChaser : location(0, 0, 0, 0, 1, 0),
locationDelta : location(0, 0, 0, 0, 1, 0),
corners : [0, 0, 0, 0, 0, 0, 0, 0], // corners x,y start from top left to top right
pixelXx : 0, // the bot right to bot left
pixelXy : 0,
transform : smoothTransform,
drag : 0.1, // drag for movements
acceleration : 0.7, // acceleration
quiet : false, // this is true when most of the movement scaling and rotation have stopped
matrix : matrix(), // main matrix
invMatrix : matrix(), // invers matrix;
mouseWorldX : 0, // the mouse location in world space
mouseWorldY : 0, // the mouse location in world space
mouseLastX : 0, // the last mouse position in screen space
mouseLastY : 0,
mouseAction : [undefined, undefined, undefined],
mouseActionOff : [undefined, undefined, undefined],
mouseWheel : undefined,
scaleSpeed : 1.1,
invScaleSpeed : 1 / 1.1,
mouseTranslate : mouseTranslate,
mouseRotate : mouseRotate,
mouseWheelZoom : mouseWheelZoom,
setMouseRotate : setUpMouseRotate,
setMouseTranslate: setUpMouseTranslate,
setWheelZoom : setWheelZoom,
setTransform : setTransform,
setDefault : function(){ this.ctx.setTransform(1, 0, 0, 1, 0, 0); },
update : updateWorld,
fitView : setLocation,
orientView : setOrientation,
ctx : undefined,
mouse : undefined,
}
return displayTransform;
})();
//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
// Clean up. Used where the IDE is on the same page.
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
startup();
/** SimpleFullCanvasMouse.js end **/
关于javascript - 如何在 map 应用中实现稳定的缩放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38674179/
我是一名优秀的程序员,十分优秀!