2012-07-16 28 views
12

Moim celem jest utworzenie wtyczki umożliwiającej powiększanie operacji na obszarze strony, podobnie jak obecnie działa Google Maps (co oznacza: przewijanie za pomocą myszy = powiększanie/zmniejszanie obszaru, klikanie & przytrzymaj & move & release = panoramowanie).CSS3 powiększanie kursora myszy

Podczas przewijania chcę wykonać operację powiększenia na środku kursora myszy.

Do tego używam transformacji macierzy CSS3 "w locie". Jedynym, ale obowiązkowym, ograniczeniem jest to, że nie mogę użyć niczego innego niż transformacja CSS3 transformata skali &, ze źródłem transformacji 0px 0px.

Przesuwanie jest poza zakresem mojego pytania, ponieważ już działa. Jeśli chodzi o powiększanie, walczę, aby dowiedzieć się, gdzie błąd jest w moim kodzie javascript.

Problem musi być gdzieś w funkcji MouseZoom.prototype.zoom, w obliczeniach tłumaczenia na osi X i osi Y.

pierwsze, tutaj jest mój kod 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> 

Jak widać, używam jQuery i wtyczki koła jquery mysz z Brandon Aarona, który można znaleźć tutaj: https://github.com/brandonaaron/jquery-mousewheel/

Oto zawartość pliku 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

Sugestie: (1) użyj jsfiddle, łatwo jest zobaczyć wynik (2) opisać "usterkę" w bardziej szczegółach. –

+0

Po prostu wskazówka - Czy widziałeś wtyczkę o nazwie zoomooz.js, jeśli nie, może mieć wiele pinpointów do tego, co chcesz zrobić - http://janne.aukia.com/zoomooz/ –

Odpowiedz

28

Korzystanie transform dostać GOOG le mapy powiększanie zachowanie na div elementu wydawało się ciekawym pomysłem, więc zapłaciłem z nim trochę =)

użyłbym transform-origin (i przypisuje jej siostra kompatybilności przeglądarki) do regulacji powiększenia do myszy położenie na div, które skalujesz. Myślę, że to może zrobić, co chcesz. umieścić kilka przykładów ryba dla ilustracji:

Regulacja transform-origin

Tak więc w funkcji możemy dynamicznie dopasować transform-origin z imageX i imageY, jeśli przekazujemy te wartości z funkcji MouseZoom (słuchacz myszy).

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

(W tym first fiddle example ja właśnie użył Twojego translateX i translateY w Transformations przekazać lokalizację myszy na elemencie div - w drugim przykładzie I przemianowano go originX i originY do odróżnienia od zmiennych tłumaczeniowych.)

Obliczanie transformacji pochodzenie

W swojej MouseZoom możemy obliczyć miejsca pochodzenia po prostu z 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); 
    } 

Więc to będzie działać idealnie jeśli pomniejszyć całkowicie przed powiększania na innej pozycji. Aby jednak móc zmieniać pochodzenie zoomu na dowolnym poziomie powiększenia, możemy łączyć funkcje pochodzenia i tłumaczenia.

Shifting ramkę powiększania (rozciągający się mój oryginalny odpowiedź)

Pochodzenie przekształcić na obraz nadal jest obliczana w ten sam sposób, ale możemy użyć osobnego translateX i translateY przesunąć ramkę zoomu (tu wprowadziłem dwa nowe zmienne, które pomagają nam rozwiązać problem - teraz mamy originX, originY, translateX i 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); 
    } 

W tym przykładzie I dostosowane oryginalnego skryptu trochę więcej i dodał second fiddle example.

Teraz powiększamy i pomniejszamy kursor myszy z dowolnego poziomu powiększenia. Ale z powodu przesunięcia ramki, kończymy przesuwanie pierwotnego elementu div ("mierzenie ziemi") ... co wygląda zabawnie, jeśli pracujesz z obiektem o ograniczonej szerokości i wysokości (powiększenie na jednym końcu, pomniejszenie o inny koniec, a my ruszyliśmy naprzód jak robak ".

Unikanie „inchworm” efekt

W tym celu można na przykład dodać ograniczenia uniknąć tak, że lewa granica obraz nie może poruszać się po prawej oryginalnych współrzędna x górna granica obraz nie może się poruszać niższa niż pierwotna pozycja y, i tak dalej dla pozostałych dwóch granic. Ale wtedy powiększenie/pomniejszenie nie będzie całkowicie związane z kursorem, ale także z krawędzią obrazu (zauważysz, że obraz przesuwa się na miejsce) w 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; 
     } 
    } 

Innym (bzdura bit) rozwiązaniem byłoby po prostu zresetować rama tłumaczyć kiedy pomniejszyć całkowicie (== Skala 1).

Nie masz jednak tego problemu, jeśli będziesz miał do czynienia z elementami ciągłymi (lewa i prawa krawędź oraz górna i dolna krawędź ze sobą połączone) lub po prostu z bardzo dużymi elementami.

Aby zakończyć wszystko z miłym dotknięciem - możemy dodać ramkę nadrzędną z ukrytym przepełnieniem wokół naszego obiektu skalowania. Tak więc obszar obrazu nie zmienia się wraz z powiększaniem. Zobacz jsfiddle example 4.

0

Zrobiliśmy biblioteki reagują na to: https://www.npmjs.com/package/react-map-interaction

obsługuje powiększanie i przesuwanie i działa zarówno na komputerach i urządzeniach mobilnych.

Źródło jest dość krótki i czytelny, ale odpowiedzieć tutaj swoje pytanie bardziej bezpośrednio, używamy tej CSS 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 

Jednym z podstawowych trików jest prawidłowo obliczania diff pomiędzy początkowym wskaźnik/mysz stan w dół i aktualny stan, gdy nastąpi ruch za pomocą dotyku/myszy. Po naciśnięciu myszy przechwyć współrzędne. Następnie przy każdym ruchu myszki (aż do podniesienia myszy) oblicz różnicę w odległości. Ta różnica polega na przesunięciu tłumaczenia, aby upewnić się, że początkowy punkt pod kursorem jest centralnym punktem zoomu.