Powiększ punkt (używając skali i tłumacz)

Chcę być w stanie powiększyć punkt pod myszką w HTML 5 płótna, jak powiększenie na Google Maps . Jak mogę to osiągnąć?

Author: Just a student, 2010-05-26

12 answers

Lepszym rozwiązaniem jest po prostu przesunięcie pozycji viewportu w oparciu o zmianę zoomu. Punkt zoomu to po prostu punkt w starym zoomie i nowym zoomie, który chcesz pozostać taki sam. Oznacza to, że widok wstępnie powiększony i widok po powiększeniu mają tę samą wartość zoompoint względem widoku. Biorąc pod uwagę, że skalujemy w stosunku do źródła. Można odpowiednio dostosować pozycję viewport:

scalechange = newscale - oldscale;
offsetX = -(zoomPointX * scalechange);
offsetY = -(zoomPointY * scalechange);

Więc naprawdę można po prostu przesuwać w dół i w prawo przy powiększeniu, o współczynnik powiększenia, w stosunku do punktu, w którym powiększono.

Tutaj wpisz opis obrazka

 93
Author: Tatarize,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-14 18:05:31

Wreszcie rozwiązałem:

var zoomIntensity = 0.2;

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = 600;
var height = 200;

var scale = 1;
var originx = 0;
var originy = 0;
var visibleWidth = width;
var visibleHeight = height;


function draw(){
    // Clear screen to white.
    context.fillStyle = "white";
    context.fillRect(originx,originy,800/scale,600/scale);
    // Draw the black square.
    context.fillStyle = "black";
    context.fillRect(50,50,100,100);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);

canvas.onmousewheel = function (event){
    event.preventDefault();
    // Get mouse offset.
    var mousex = event.clientX - canvas.offsetLeft;
    var mousey = event.clientY - canvas.offsetTop;
    // Normalize wheel to +1 or -1.
    var wheel = event.wheelDelta/120;

    // Compute zoom factor.
    var zoom = Math.exp(wheel*zoomIntensity);
    
    // Translate so the visible origin is at the context's origin.
    context.translate(originx, originy);
  
    // Compute the new visible origin. Originally the mouse is at a
    // distance mouse/scale from the corner, we want the point under
    // the mouse to remain in the same place after the zoom, but this
    // is at mouse/new_scale away from the corner. Therefore we need to
    // shift the origin (coordinates of the corner) to account for this.
    originx -= mousex/(scale*zoom) - mousex/scale;
    originy -= mousey/(scale*zoom) - mousey/scale;
    
    // Scale it (centered around the origin due to the trasnslate above).
    context.scale(zoom, zoom);
    // Offset the visible origin to it's proper position.
    context.translate(-originx, -originy);

    // Update scale and others.
    scale *= zoom;
    visibleWidth = width / scale;
    visibleHeight = height / scale;
}
<canvas id="canvas" width="600" height="200"></canvas>

Kluczem, jak zauważył @Tatarize, jest obliczenie pozycji osi w taki sposób, aby punkt powiększenia (wskaźnik myszy) pozostał w tym samym miejscu po powiększeniu.

Początkowo mysz znajduje się w odległości mouse/scale od rogu, chcemy, aby punkt pod myszką pozostał w tym samym miejscu po powiększeniu, ale to jest w odległości mouse/new_scale od rogu. Dlatego musimy przesunąć origin (współrzędne rogu), aby uwzględnić to.

originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
scale *= zomm

Pozostały kod musi następnie zastosować skalowanie i przetłumaczyć na kontekst rysowania, aby jego pochodzenie pokrywało się z narożnikiem obszaru roboczego.

 47
Author: csiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-25 10:46:19

Jest to właściwie bardzo trudny problem (matematycznie) i pracuję nad tym samym prawie. Zadałem podobne pytanie na Stackoverflow, ale nie dostałem odpowiedzi, ale posted in DocType (StackOverflow for HTML/CSS) and got a response. Check it out http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example

Jestem w trakcie budowania wtyczki jQuery, która to robi (Google Maps style zoom przy użyciu przekształceń CSS3). Mam zoom do myszki kursor działa dobrze, wciąż próbując dowiedzieć się, jak umożliwić użytkownikowi przeciąganie płótna, tak jak można to zrobić w Google Maps. Kiedy to działa będę Kod Pocztowy tutaj, ale sprawdź powyżej link do części myszy zoom-to-point.

Nie zdawałem sobie sprawy, że istnieją metody skalowania i tłumaczenia w kontekście Canvas, to samo można osiągnąć za pomocą CSS3 np. używanie jQuery:

$('div.canvasContainer > canvas')
    .css('-moz-transform', 'scale(1) translate(0px, 0px)')
    .css('-webkit-transform', 'scale(1) translate(0px, 0px)')
    .css('-o-transform', 'scale(1) translate(0px, 0px)')
    .css('transform', 'scale(1) translate(0px, 0px)');

Upewnij się, że ustawiłeś CSS3 transform-origin na 0, 0 (-moz-transform-origin: 0 0). Korzystanie z CSS3 upewnij się, że kontener DIV jest ustawiony na overflow: hidden, aby zatrzymać wylewanie się powiększonych krawędzi z boków.

To, czy używasz przekształceń CSS3, czy własnych metod skalowania i tłumaczenia canvas, zależy od ciebie, ale sprawdź powyższy link dla obliczeń.


Update: Meh! Po prostu umieszczę tutaj kod, a nie każę ci podążać za linkiem:

$(document).ready(function()
{
    var scale = 1;  // scale of the image
    var xLast = 0;  // last x location on the screen
    var yLast = 0;  // last y location on the screen
    var xImage = 0; // last x location on the image
    var yImage = 0; // last y location on the image

    // if mousewheel is moved
    $("#mosaicContainer").mousewheel(function(e, delta)
    {
        // find current location on screen 
        var xScreen = e.pageX - $(this).offset().left;
        var yScreen = e.pageY - $(this).offset().top;

        // find current location on the image at the current scale
        xImage = xImage + ((xScreen - xLast) / scale);
        yImage = yImage + ((yScreen - yLast) / scale);

        // determine the new scale
        if (delta > 0)
        {
            scale *= 2;
        }
        else
        {
            scale /= 2;
        }
        scale = scale < 1 ? 1 : (scale > 64 ? 64 : scale);

        // determine the location on the screen at the new scale
        var xNew = (xScreen - xImage) / scale;
        var yNew = (yScreen - yImage) / scale;

        // save the current screen location
        xLast = xScreen;
        yLast = yScreen;

        // redraw
        $(this).find('div').css('-moz-transform', 'scale(' + scale + ')' + 'translate(' + xNew + 'px, ' + yNew + 'px' + ')')
                           .css('-moz-transform-origin', xImage + 'px ' + yImage + 'px')
        return false;
    });
});

Będziesz oczywiście musiał dostosować go do użycia skali canvas i Tłumacz metody.


Update 2: właśnie zauważyłem, że używam transform-origin razem z translate. Udało mi się zaimplementować wersję, która po prostu używa skali i tłumaczy na własną rękę, sprawdź to tutaj http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Poczekaj na pobranie obrazów, a następnie użyj kółka myszy, aby powiększyć, obsługuje również panoramowanie, przeciągając obraz. Korzysta z transformat CSS3, ale powinieneś móc korzystać z tych samych obliczeń na płótno.

 26
Author: Sunday Ironfoot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-28 06:51:54

Natknąłem się na ten problem używając c++, którego prawdopodobnie nie powinienem był mieć, na początku używałem tylko macierzy OpenGL...w każdym razie, jeśli używasz kontrolki, której źródłem jest lewy górny róg, i chcesz przesuwać / powiększać jak google maps, oto układ (używając allegro jako obsługi zdarzenia): {]}

// initialize
double originx = 0; // or whatever its base offset is
double originy = 0; // or whatever its base offset is
double zoom = 1;

.
.
.

main(){

    // ...set up your window with whatever
    //  tool you want, load resources, etc

    .
    .
    .
    while (running){
        /* Pan */
        /* Left button scrolls. */
        if (mouse == 1) {
            // get the translation (in window coordinates)
            double scroll_x = event.mouse.dx; // (x2-x1) 
            double scroll_y = event.mouse.dy; // (y2-y1) 

            // Translate the origin of the element (in window coordinates)      
            originx += scroll_x;
            originy += scroll_y;
        }

        /* Zoom */ 
        /* Mouse wheel zooms */
        if (event.mouse.dz!=0){    
            // Get the position of the mouse with respect to 
            //  the origin of the map (or image or whatever).
            // Let us call these the map coordinates
            double mouse_x = event.mouse.x - originx;
            double mouse_y = event.mouse.y - originy;

            lastzoom = zoom;

            // your zoom function 
            zoom += event.mouse.dz * 0.3 * zoom;

            // Get the position of the mouse
            // in map coordinates after scaling
            double newx = mouse_x * (zoom/lastzoom);
            double newy = mouse_y * (zoom/lastzoom);

            // reverse the translation caused by scaling
            originx += mouse_x - newx;
            originy += mouse_y - newy;
        }
    }
}  

.
.
.

draw(originx,originy,zoom){
    // NOTE:The following is pseudocode
    //          the point is that this method applies so long as
    //          your object scales around its top-left corner
    //          when you multiply it by zoom without applying a translation.

    // draw your object by first scaling...
    object.width = object.width * zoom;
    object.height = object.height * zoom;

    //  then translating...
    object.X = originx;
    object.Y = originy; 
}
 7
Author: Aleksandr Albert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-19 04:55:46

Oto moje rozwiązanie dla obrazu zorientowanego na środek:

var MIN_SCALE = 1;
var MAX_SCALE = 5;
var scale = MIN_SCALE;

var offsetX = 0;
var offsetY = 0;

var $image     = $('#myImage');
var $container = $('#container');

var areaWidth  = $container.width();
var areaHeight = $container.height();

$container.on('wheel', function(event) {
    event.preventDefault();
    var clientX = event.originalEvent.pageX - $container.offset().left;
    var clientY = event.originalEvent.pageY - $container.offset().top;

    var nextScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale - event.originalEvent.deltaY / 100));

    var percentXInCurrentBox = clientX / areaWidth;
    var percentYInCurrentBox = clientY / areaHeight;

    var currentBoxWidth  = areaWidth / scale;
    var currentBoxHeight = areaHeight / scale;

    var nextBoxWidth  = areaWidth / nextScale;
    var nextBoxHeight = areaHeight / nextScale;

    var deltaX = (nextBoxWidth - currentBoxWidth) * (percentXInCurrentBox - 0.5);
    var deltaY = (nextBoxHeight - currentBoxHeight) * (percentYInCurrentBox - 0.5);

    var nextOffsetX = offsetX - deltaX;
    var nextOffsetY = offsetY - deltaY;

    $image.css({
        transform : 'scale(' + nextScale + ')',
        left      : -1 * nextOffsetX * nextScale,
        right     : nextOffsetX * nextScale,
        top       : -1 * nextOffsetY * nextScale,
        bottom    : nextOffsetY * nextScale
    });

    offsetX = nextOffsetX;
    offsetY = nextOffsetY;
    scale   = nextScale;
});
body {
    background-color: orange;
}
#container {
    margin: 30px;
    width: 500px;
    height: 500px;
    background-color: white;
    position: relative;
    overflow: hidden;
}
img {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    max-width: 100%;
    max-height: 100%;
    margin: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div id="container">
    <img id="myImage" src="http://s18.postimg.org/eplac6dbd/mountain.jpg">
</div>
 6
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-16 06:15:21

Podoba mi się odpowiedź Tatarize, ale podam alternatywę. Jest to trywialny problem algebry liniowej, a metoda, którą przedstawiam, działa dobrze z pan, zoom, pochylenie, itp. Oznacza to, że działa dobrze, jeśli twój obraz jest już przekształcony.

Gdy macierz jest skalowana, skala jest w punkcie (0, 0). Tak więc, jeśli masz obraz i przeskaluj go o współczynnik 2, prawy dolny punkt podwoi się w obu kierunkach x i y (używając konwencji, że [0, 0] jest lewym górnym rogu obraz).

Jeśli zamiast tego chcesz powiększyć obraz wokół środka, To rozwiązanie jest następujące: (1) Przetłumacz obraz tak, że jego środek znajduje się w (0, 0); (2) przeskaluj obraz według współczynników x i y; (3) Przetłumacz obraz z powrotem. tj.

myMatrix
  .translate(image.width / 2, image.height / 2)    // 3
  .scale(xFactor, yFactor)                         // 2
  .translate(-image.width / 2, -image.height / 2); // 1
Bardziej abstrakcyjnie, ta sama strategia działa dla każdego punktu. Jeśli na przykład chcesz przeskalować obraz w punkcie P:
myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y);

I wreszcie, jeśli obraz jest już w jakiś sposób przekształcony (na przykład, jeśli jest obrócony, Przekrzywione, przetłumaczone lub skalowane), następnie należy zachować obecną transformację. W szczególności, transformata zdefiniowana powyżej musi być pomnożona (lub pomnożona przez prawo) przez transformatę bieżącą.

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y)
  .multiply(myMatrix);
Proszę bardzo. Oto plunk, który pokazuje to w akcji. Przewiń kółkiem myszy na kropkach, a zobaczysz, że konsekwentnie pozostają na miejscu. (Testowane tylko w Chrome.) http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview
 6
Author: avejidah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 14:37:58

Chcę tu zamieścić kilka informacji dla tych, którzy samodzielnie rysują obraz i przesuwają go-powiększają.

Może to być przydatne, gdy chcesz przechowywać powiększenia i pozycję widoku.

Oto szuflada:

function redraw_ctx(){
   self.ctx.clearRect(0,0,canvas_width, canvas_height)
   self.ctx.save()
   self.ctx.scale(self.data.zoom, self.data.zoom) // 
   self.ctx.translate(self.data.position.left, self.data.position.top) // position second
   // Here We draw useful scene My task - image:
   self.ctx.drawImage(self.img ,0,0) // position 0,0 - we already prepared
   self.ctx.restore(); // Restore!!!
}

Uwaga Skala musi być pierwsza.

A oto zoomer:

function zoom(zf, px, py){
    // zf - is a zoom factor, which in my case was one of (0.1, -0.1)
    // px, py coordinates - is point within canvas 
    // eg. px = evt.clientX - canvas.offset().left
    // py = evt.clientY - canvas.offset().top
    var z = self.data.zoom;
    var x = self.data.position.left;
    var y = self.data.position.top;

    var nz = z + zf; // getting new zoom
    var K = (z*z + z*zf) // putting some magic

    var nx = x - ( (px*zf) / K ); 
    var ny = y - ( (py*zf) / K);

    self.data.position.left = nx; // renew positions
    self.data.position.top = ny;   
    self.data.zoom = nz; // ... and zoom
    self.redraw_ctx(); // redraw context
    }

I, oczywiście, potrzebowalibyśmy draggera:

this.my_cont.mousemove(function(evt){
    if (is_drag){
        var cur_pos = {x: evt.clientX - off.left,
                       y: evt.clientY - off.top}
        var diff = {x: cur_pos.x - old_pos.x,
                    y: cur_pos.y - old_pos.y}

        self.data.position.left += (diff.x / self.data.zoom);  // we want to move the point of cursor strictly
        self.data.position.top += (diff.y / self.data.zoom);

        old_pos = cur_pos;
        self.redraw_ctx();

    }


})
 3
Author: Vasiliy Stavenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-15 03:34:02

Oto alternatywny sposób, aby to zrobić, który używa setTransform() zamiast scale () I translate (). Wszystko jest przechowywane w tym samym obiekcie. Zakłada się, że obszar roboczy na stronie ma wartość 0,0, w przeciwnym razie musisz odjąć jego pozycję od koordów strony.

this.zoomIn = function (pageX, pageY) {
    var zoomFactor = 1.1;
    this.scale = this.scale * zoomFactor;
    this.lastTranslation = {
        x: pageX - (pageX - this.lastTranslation.x) * zoomFactor,
        y: pageY - (pageY - this.lastTranslation.y) * zoomFactor
    };
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    this.lastTranslation.x,
                                    this.lastTranslation.y);
};
this.zoomOut = function (pageX, pageY) {
    var zoomFactor = 1.1;
    this.scale = this.scale / zoomFactor;
    this.lastTranslation = {
        x: pageX - (pageX - this.lastTranslation.x) / zoomFactor,
        y: pageY - (pageY - this.lastTranslation.y) / zoomFactor
    };
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    this.lastTranslation.x,
                                    this.lastTranslation.y);
};

Kod towarzyszący do obsługi panoramowania:

this.startPan = function (pageX, pageY) {
    this.startTranslation = {
        x: pageX - this.lastTranslation.x,
        y: pageY - this.lastTranslation.y
    };
};
this.continuePan = function (pageX, pageY) {
    var newTranslation = {x: pageX - this.startTranslation.x,
                          y: pageY - this.startTranslation.y};
    this.canvasContext.setTransform(this.scale, 0, 0, this.scale,
                                    newTranslation.x, newTranslation.y);
};
this.endPan = function (pageX, pageY) {
    this.lastTranslation = {
        x: pageX - this.startTranslation.x,
        y: pageY - this.startTranslation.y
    };
};

Aby uzyskać odpowiedź samodzielnie, należy wziąć pod uwagę, że te same współrzędne strony muszą pasować do tych samych współrzędnych płótna przed i po powiększeniu. Więc możesz zrobić trochę algebra zaczynająca się od tego równania:

(pageCoords - translation) / scale = canvasCoords

 3
Author: chad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-29 02:51:16
if(wheel > 0) {
    this.scale *= 1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1.1 - 1);
    this.offsetY -= (mouseY - this.offsetY) * (1.1 - 1);
}
else {
    this.scale *= 1/1.1; 
    this.offsetX -= (mouseX - this.offsetX) * (1/1.1 - 1);
    this.offsetY -= (mouseY - this.offsetY) * (1/1.1 - 1);
}
 2
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-30 15:23:49

Oto implementacja kodu odpowiedzi @ tatarize za pomocą PIXI.js. Mam viewport patrząc na Część bardzo dużego obrazu(np. Google maps style).

$canvasContainer.on('wheel', function (ev) {

    var scaleDelta = 0.02;
    var currentScale = imageContainer.scale.x;
    var nextScale = currentScale + scaleDelta;

    var offsetX = -(mousePosOnImage.x * scaleDelta);
    var offsetY = -(mousePosOnImage.y * scaleDelta);

    imageContainer.position.x += offsetX;
    imageContainer.position.y += offsetY;

    imageContainer.scale.set(nextScale);

    renderer.render(stage);
});
  • $canvasContainer jest moim kontenerem html.
  • imageContainer to mój Pojemnik PIXI, w którym znajduje się obraz.
  • mousePosOnImage to pozycja myszy względem całego obrazu(nie tylko portu widoku).

Oto jak zdobyłem pozycję myszy:

  imageContainer.on('mousemove', _.bind(function(ev) {
    mousePosOnImage = ev.data.getLocalPosition(imageContainer);
    mousePosOnViewport.x = ev.data.originalEvent.offsetX;
    mousePosOnViewport.y = ev.data.originalEvent.offsetY;
  },self));
 2
Author: Mirror318,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 22:46:20

Musisz uzyskać punkt w przestrzeni świata (w przeciwieństwie do przestrzeni ekranu) przed i po powiększeniu, a następnie przetłumaczyć przez deltę.

mouse_world_position = to_world_position(mouse_screen_position);
zoom();
mouse_world_position_new = to_world_position(mouse_screen_position);
translation += mouse_world_position_new - mouse_world_position;

Pozycja myszy znajduje się w przestrzeni ekranu, więc musisz przekształcić ją w przestrzeń świata. Proste przekształcenie powinno być podobne do tego:

world_position = screen_position / scale - translation
 0
Author: Enhex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-29 22:00:37

Możesz użyć funkcji scrollto (x, y), aby obsłużyć pozycję paska przewijania aż do punktu, który ma być pokazany po powiększeniu.do znalezienia pozycji myszy użyj zdarzenia.clientX i event.klientów. to ci pomoże

 0
Author: Mamo Ghandi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 08:06:27