[英]CSS3 zooming on mouse cursor
我的目標是創建一個插件,可以在頁面區域上啟用縮放和平移操作,就像 Google 地圖目前的工作方式一樣(意思是:用鼠標滾動 = 放大/縮小區域,單擊並按住並移動並釋放 = 平移).
滾動時,我希望以鼠標光標為中心進行縮放操作。
為此,我使用了即時 CSS3 矩陣轉換。 唯一但強制性的約束是我不能使用 CSS3 平移和縮放轉換以外的任何東西,轉換原點為 0px 0px。
平移不在我的問題范圍內,因為我已經在使用它了。 說到縮放,我很難找出我的 Javascript 代碼中的錯誤所在。
問題一定出在MouseZoom.prototype.zoom
函數的某處,在計算 x 軸和 y 軸上的平移時。
首先,這是我的 HTML 代碼:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width = device-width, initial-scale = 1.0, user-scalable = no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script src="jquery.mousewheel.min.js"></script>
<script src="StackOverflow.js"></script>
<style type="text/css" media="all">
#drawing {
position: absolute;
top: 0px;
left: 0px;
right:0;
bottom:0;
z-index: 0;
background: url(http://catmacros.files.wordpress.com/2009/09/cats_banzai.jpg) no-repeat;
background-position: 50% 50%;
}
</style>
<title>Test</title>
</head>
<body>
<div id="drawing"></div>
<script>
var renderer = new ZoomPanRenderer("drawing");
</script>
</body>
</html>
如您所見,我正在使用 Jquery 和來自 Brandon Aaron 的 jquery 鼠標滾輪插件,可在此處找到: https ://github.com/brandonaaron/jquery-mousewheel/
以下是 StackOverflow.js 文件的內容:
/*****************************************************
* Transformations
****************************************************/
function Transformations(translateX, translateY, scale){
this.translateX = translateX;
this.translateY = translateY;
this.scale = scale;
}
/* Getters */
Transformations.prototype.getScale = function(){ return this.scale; }
Transformations.prototype.getTranslateX = function(){ return this.translateX; }
Transformations.prototype.getTranslateY = function(){ return this.translateY; }
/*****************************************************
* Zoom Pan Renderer
****************************************************/
function ZoomPanRenderer(elementId){
this.zooming = undefined;
this.elementId = elementId;
this.current = new Transformations(0, 0, 1);
this.last = new Transformations(0, 0, 1);
new ZoomPanEventHandlers(this);
}
/* setters */
ZoomPanRenderer.prototype.setCurrentTransformations = function(t){ this.current = t; }
ZoomPanRenderer.prototype.setZooming = function(z){ this.zooming = z; }
/* getters */
ZoomPanRenderer.prototype.getCurrentTransformations = function(){ return this.current; }
ZoomPanRenderer.prototype.getZooming = function(){ return this.zooming; }
ZoomPanRenderer.prototype.getLastTransformations = function(){ return this.last; }
ZoomPanRenderer.prototype.getElementId = function(){ return this.elementId; }
/* Rendering */
ZoomPanRenderer.prototype.getTransform3d = function(t){
var transform3d = "matrix3d(";
transform3d+= t.getScale().toFixed(10) + ",0,0,0,";
transform3d+= "0," + t.getScale().toFixed(10) + ",0,0,";
transform3d+= "0,0,1,0,";
transform3d+= t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10) + ",0,1)";
return transform3d;
}
ZoomPanRenderer.prototype.getTransform2d = function(t){
var transform3d = "matrix(";
transform3d+= t.getScale().toFixed(10) + ",0,0," + t.getScale().toFixed(10) + "," + t.getTranslateX().toFixed(10) + "," + t.getTranslateY().toFixed(10) + ")";
return transform3d;
}
ZoomPanRenderer.prototype.applyTransformations = function(t){
var elem = $("#" + this.getElementId());
elem.css("transform-origin", "0px 0px");
elem.css("-ms-transform-origin", "0px 0px");
elem.css("-o-transform-origin", "0px 0px");
elem.css("-moz-transform-origin", "0px 0px");
elem.css("-webkit-transform-origin", "0px 0px");
var transform2d = this.getTransform2d(t);
elem.css("transform", transform2d);
elem.css("-ms-transform", transform2d);
elem.css("-o-transform", transform2d);
elem.css("-moz-transform", transform2d);
elem.css("-webkit-transform", this.getTransform3d(t));
}
/*****************************************************
* Event handler
****************************************************/
function ZoomPanEventHandlers(renderer){
this.renderer = renderer;
/* Disable scroll overflow - safari */
document.addEventListener('touchmove', function(e) { e.preventDefault(); }, false);
/* Disable default drag opeartions on the element (FF makes it ready for save)*/
$("#" + renderer.getElementId()).bind('dragstart', function(e) { e.preventDefault(); });
/* Add mouse wheel handler */
$("#" + renderer.getElementId()).bind("mousewheel", function(event, delta) {
if(renderer.getZooming()==undefined){
var offsetLeft = $("#" + renderer.getElementId()).offset().left;
var offsetTop = $("#" + renderer.getElementId()).offset().top;
var zooming = new MouseZoom(renderer.getCurrentTransformations(), event.pageX, event.pageY, offsetLeft, offsetTop, delta);
renderer.setZooming(zooming);
var newTransformation = zooming.zoom();
renderer.applyTransformations(newTransformation);
renderer.setCurrentTransformations(newTransformation);
renderer.setZooming(undefined);
}
return false;
});
}
/*****************************************************
* Mouse zoom
****************************************************/
function MouseZoom(t, mouseX, mouseY, offsetLeft, offsetTop, delta){
this.current = t;
this.offsetLeft = offsetLeft;
this.offsetTop = offsetTop;
this.mouseX = mouseX;
this.mouseY = mouseY;
this.delta = delta;
}
MouseZoom.prototype.zoom = function(){
var previousScale = this.current.getScale();
var newScale = previousScale + this.delta/5;
if(newScale<1){
newScale = 1;
}
var ratio = newScale / previousScale;
var imageX = this.mouseX - this.offsetLeft;
var imageY = this.mouseY - this.offsetTop;
var previousTx = - this.current.getTranslateX() * previousScale;
var previousTy = - this.current.getTranslateY() * previousScale;
var previousDx = imageX * previousScale;
var previousDy = imageY * previousScale;
var newTx = (previousTx * ratio + previousDx * (ratio - 1)) / newScale;
var newTy = (previousTy * ratio + previousDy * (ratio - 1)) / newScale;
return new Transformations(-newTx, -newTy, newScale);
}
使用transform
在div
元素上獲得谷歌地圖縮放行為似乎是一個有趣的想法,所以我付了一點錢 =)
我會使用transform-origin
(及其用於瀏覽器兼容性的姊妹屬性)將縮放調整到您正在縮放的 div 上的鼠標位置。 我認為這可以做你想做的。 我在小提琴上放了一些例子來說明:
調整transform-origin
因此,在您的applyTransformations
函數中,如果我們從MouseZoom
(鼠標偵聽器)函數傳遞此值,我們可以從imageX
和imageY
動態調整transform-origin
。
var orig = t.getTranslateX().toFixed() + "px " + t.getTranslateY().toFixed() + "px";
elem.css("transform-origin", orig);
elem.css("-ms-transform-origin", orig);
elem.css("-o-transform-origin", orig);
elem.css("-moz-transform-origin", orig);
elem.css("-webkit-transform-origin", orig);
(在第一個小提琴示例中,我只是在Transformations
中使用了translateX
和translateY
來傳遞鼠標在 div 元素上的位置——在第二個示例中,我將其重命名為originX
和originY
以區別於翻譯變量。)
計算變換原點
在您的MouseZoom
,我們可以簡單地使用imageX/previousScale
計算原點位置。
MouseZoom.prototype.zoom = function(){
var previousScale = this.current.getScale();
var newScale = previousScale + this.delta/10;
if(newScale<1){
newScale = 1;
}
var ratio = newScale / previousScale;
var imageX = this.mouseX - this.offsetLeft;
var imageY = this.mouseY - this.offsetTop;
var newTx = imageX/previousScale;
var newTy = imageY/previousScale;
return new Transformations(newTx, newTy, newScale);
}
因此,如果您在放大到不同位置之前完全縮小,這將非常有效。 但是為了能夠在任何縮放級別更改縮放原點,我們可以結合原點和平移功能。
移動縮放框(擴展我原來的答案)
圖像上的變換原點仍然以相同的方式計算,但我們使用單獨的 translateX 和 translateY 來移動縮放框(這里我引入了兩個新變量來幫助我們完成這個技巧 - 所以現在我們有originX
, originY
, translateX
和translateY
).
MouseZoom.prototype.zoom = function(){
// current scale
var previousScale = this.current.getScale();
// new scale
var newScale = previousScale + this.delta/10;
// scale limits
var maxscale = 20;
if(newScale<1){
newScale = 1;
}
else if(newScale>maxscale){
newScale = maxscale;
}
// current cursor position on image
var imageX = (this.mouseX - this.offsetLeft).toFixed(2);
var imageY = (this.mouseY - this.offsetTop).toFixed(2);
// previous cursor position on image
var prevOrigX = (this.current.getOriginX()*previousScale).toFixed(2);
var prevOrigY = (this.current.getOriginY()*previousScale).toFixed(2);
// previous zooming frame translate
var translateX = this.current.getTranslateX();
var translateY = this.current.getTranslateY();
// set origin to current cursor position
var newOrigX = imageX/previousScale;
var newOrigY = imageY/previousScale;
// move zooming frame to current cursor position
if ((Math.abs(imageX-prevOrigX)>1 || Math.abs(imageY-prevOrigY)>1) && previousScale < maxscale) {
translateX = translateX + (imageX-prevOrigX)*(1-1/previousScale);
translateY = translateY + (imageY-prevOrigY)*(1-1/previousScale);
}
// stabilize position by zooming on previous cursor position
else if(previousScale != 1 || imageX != prevOrigX && imageY != prevOrigY) {
newOrigX = prevOrigX/previousScale;
newOrigY = prevOrigY/previousScale;
}
return new Transformations(newOrigX, newOrigY, translateX, translateY, newScale);
}
對於這個例子,我稍微調整了你的原始腳本並添加了第二個小提琴示例。
現在我們從任何縮放級別放大和縮小鼠標光標。 但是由於幀偏移,我們最終移動了原始 div(“測量地球”)...如果您使用寬度和高度有限的對象(在一端放大,在另一端縮小),這看起來很有趣另一端,我們像尺蠖一樣向前移動)。
避免“尺蠖”效應
為避免這種情況,您可以添加限制,例如,左圖像邊框不能移動到其原始 x 坐標的右側,頂部圖像邊框不能移動到其原始 y 位置以下,其他兩個邊框等等。 但是在示例 3中,縮放/縮小不會完全綁定到光標,還會綁定到圖像的邊緣(您會注意到圖像滑入到位)。
if(this.delta <= 0){
var width = 500; // image width
var height = 350; // image height
if(translateX+newOrigX+(width - newOrigX)*newScale <= width){
translateX = 0;
newOrigX = width;
}
else if (translateX+newOrigX*(1-newScale) >= 0){
translateX = 0;
newOrigX = 0;
}
if(translateY+newOrigY+(height - newOrigY)*newScale <= height){
translateY = 0;
newOrigY = height;
}
else if (translateY+newOrigY*(1-newScale) >= 0){
translateY = 0;
newOrigY = 0;
}
}
另一個(有點蹩腳的)選項是在完全縮小時簡單地重置幀轉換(scale==1)。
但是,如果您要處理連續元素(左右邊緣以及上下邊緣綁定在一起)或僅處理非常大的元素,則不會遇到此問題。
為了巧妙地完成所有事情——我們可以在我們的縮放對象周圍添加一個帶有隱藏溢出的父框架。 所以圖像區域不會隨着縮放而改變。 請參閱jsfiddle 示例 4 。
我們為此制作了一個反應庫: https ://www.npmjs.com/package/react-map-interaction
它處理縮放和平移,並可在移動設備和桌面設備上使用。
源代碼相當簡短且易讀,但為了更直接地回答您的問題,我們使用此 CSS 轉換:
const transform = `translate(${translation.x}px, ${translation.y}px) scale(${scale})`;
const style = {
transform: transform,
transformOrigin: '0 0 '
};
// render the div with that style
主要技巧之一是正確計算初始指針/鼠標按下狀態與發生觸摸/鼠標移動時的當前狀態之間的差異。 當發生鼠標按下時,捕獲坐標。 然后在每次鼠標移動時(直到鼠標抬起)計算距離的差異。 該差異是您需要偏移平移的內容,以確保光標下的初始點是縮放的焦點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.