2012-07-16 40 views
12

Mục tiêu của tôi là tạo plugin cho phép phóng to & hoạt động xoay vòng trên khu vực trang, giống như cách Google Maps hiện đang hoạt động (có nghĩa là: cuộn bằng chuột = phóng to/thu nhỏ của khu vực, nhấp & giữ & di chuyển & release = panning).Phóng to CSS3 trên con trỏ chuột

Khi cuộn, tôi muốn có hoạt động thu phóng được căn giữa trên con trỏ chuột.

Đối với điều này, tôi sử dụng các phép biến đổi ma trận CSS3 on-the-fly. Hạn chế duy nhất, nhưng bắt buộc, là tôi không thể sử dụng bất cứ điều gì khác ngoài CSS3 dịch & biến đổi quy mô, với nguồn gốc biến đổi là 0px 0px.

Di chuyển nằm ngoài phạm vi câu hỏi của tôi, vì tôi đã làm việc đó. Khi nói đến phóng to, tôi đang đấu tranh để tìm ra nơi mà trục trặc là trong mã javascript của tôi.

Sự cố phải ở đâu đó trong hàm MouseZoom.prototype.zoom, trong phép tính bản dịch trên trục x và trục y.

Thứ nhất, đây là mã HTML của tôi:

<!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> 

Như bạn thấy, tôi đang sử dụng Jquery và các plugin bánh jquery chuột từ Brandon Aaron, có thể được tìm thấy ở đây: https://github.com/brandonaaron/jquery-mousewheel/

Dưới đây là nội dung của tập tin 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); 
} 
+2

Gợi ý: (1) sử dụng jsfiddle, thật dễ dàng để xem kết quả (2) mô tả "trục trặc" để biết thêm chi tiết. –

+0

Chỉ cần một mẹo - Bạn đã thấy plugin có tên là zoomooz.js nếu không, nó có thể có rất nhiều trình xác định cho những gì bạn muốn - http://janne.aukia.com/zoomooz/ –

Trả lời

28

Sử dụng transform để có được một goog le các bản đồ về phóng to hành vi trên một yếu tố div có vẻ như một ý tưởng thú vị, vì vậy tôi payed với nó một chút =)

Tôi sẽ sử dụng transform-origin (và em gái của mình thuộc tính cho khả năng tương thích trình duyệt) để điều chỉnh zoom để chuột vị trí trên div mà bạn đang mở rộng quy mô. Tôi nghĩ rằng điều này có thể làm những gì bạn muốn. tôi đặt một số ví dụ về fiddle để minh hoạ:

Điều chỉnh transform-origin

Vì vậy, trong applyTransformations chức năng của bạn, chúng tôi có thể điều chỉnh transform-origin động từ imageXimageY, nếu chúng ta vượt qua giá trị này từ MouseZoom function (chuột nghe).

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); 

(Trong first fiddle example này tôi chỉ sử dụng translateX của bạn và translateY trong Transformations để vượt qua vị trí của chuột trên phần tử div - trong ví dụ thứ hai tôi đổi tên nó để originXoriginY để phân biệt từ các biến dịch.)

tính chuyển đổi nguồn gốc

Tại nơi MouseZoom chúng ta có thể tính toán gốc của bạn chỉ đơn giản với 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); 
    } 

Vì vậy, điều này sẽ hoạt động hoàn hảo nếu bạn thu nhỏ hoàn toàn trước khi phóng to ở vị trí khác. Nhưng để có thể thay đổi nguồn gốc thu phóng ở mọi mức thu phóng, chúng tôi có thể kết hợp nguồn gốc và chức năng dịch thuật.

Chuyển khung zoom (mở rộng câu trả lời ban đầu của tôi)

Các chuyển đổi nguồn gốc vào hình ảnh vẫn được tính theo cách tương tự nhưng chúng tôi sử dụng một translateX riêng biệt và translateY chuyển khung zoom (ở đây tôi giới thiệu hai các biến mới giúp chúng tôi thực hiện thủ thuật - vì vậy hiện tại chúng tôi có originX, originY, translateXtranslateY).

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); 
    } 

Ví dụ này tôi đã điều chỉnh tập lệnh gốc của bạn nhiều hơn một chút và thêm second fiddle example.

Bây giờ, chúng tôi phóng to và thu nhỏ con trỏ chuột từ mọi mức thu phóng. Nhưng vì sự thay đổi khung hình, chúng tôi sẽ di chuyển div ban đầu xung quanh ("đo trái đất") ... trông có vẻ buồn cười nếu bạn làm việc với vật thể có chiều rộng giới hạn và cao (zoom-in ở một đầu, thu nhỏ tại một kết thúc khác, và chúng tôi tiến về phía trước giống như một con sâu).

Tránh "inchworm" hiệu ứng

Để tránh điều này bạn có thể ví dụ như thêm những hạn chế để các biên giới hình ảnh bên trái không thể di chuyển ở bên phải của x ban đầu của nó phối hợp, biên giới hình ảnh đầu không thể di chuyển thấp hơn vị trí y ban đầu của nó, v.v. cho hai đường biên kia. Nhưng sau đó zoom/out sẽ không được hoàn toàn bị ràng buộc với con trỏ, mà còn bởi các cạnh của hình ảnh (bạn sẽ thấy slide hình ảnh vào vị trí) trong example 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; 
     } 
    } 

Tùy chọn khác (một chút crappy) sẽ chỉ đơn giản là đặt lại khung dịch khi bạn thu nhỏ hoàn toàn (scale == 1).

Tuy nhiên, bạn sẽ không gặp phải vấn đề này nếu bạn sẽ xử lý các phần tử liên tục (cạnh trái và cạnh phải và cạnh trên và cạnh dưới cùng) hoặc chỉ với các phần tử cực lớn.

Để hoàn thành mọi thứ với một liên lạc tốt đẹp - chúng tôi có thể thêm khung cha với tràn ẩn xung quanh đối tượng mở rộng của chúng tôi. Vì vậy, khu vực hình ảnh không thay đổi với phóng to. Xem jsfiddle example 4.

0

Chúng tôi đã làm một phản ứng thư viện cho việc này: https://www.npmjs.com/package/react-map-interaction

nó xử lý phóng to và panning và hoạt động trên cả điện thoại di động và máy tính để bàn.

Nguồn là khá ngắn và dễ đọc, nhưng để trả lời câu hỏi của bạn ở đây trực tiếp hơn, chúng tôi sử dụng CSS này transform:

const transform = `translate(${translation.x}px, ${translation.y}px) scale(${scale})`; 
const style = { 
    transform: transform, 
    transformOrigin: '0 0 ' 
}; 

// render the div with that style 

Một trong những thủ đoạn chính được đúng tính diff giữa con trỏ ban đầu/chuột xuống trạng thái và trạng thái hiện tại khi một di chuyển cảm ứng/chuột xảy ra. Khi chuột rơi xảy ra, hãy chụp tọa độ. Sau đó, trên mỗi di chuyển chuột (cho đến khi một con chuột lên) tính toán sự khác biệt trong khoảng cách. Sự khác biệt đó là những gì bạn cần để bù đắp bản dịch theo để đảm bảo điểm ban đầu bên dưới con trỏ của bạn là tiêu điểm của thu phóng.

Các vấn đề liên quan