Увеличьте точку (используя масштаб и перевод) - PullRequest
137 голосов
/ 26 мая 2010

Я хочу иметь возможность увеличивать точку под мышью на холсте HTML 5, как увеличение Google Maps . Как мне этого добиться?

Ответы [ 12 ]

107 голосов
/ 23 мая 2015

Лучшее решение - просто переместить положение области просмотра на основе изменения масштаба. Точка зума - это просто точка в старом и новом зумах, которую вы хотите оставить прежней. Это означает, что область просмотра предварительно увеличена, а область просмотра после увеличения имеет одинаковую точку масштабирования относительно области просмотра. Учитывая, что мы масштабируемся относительно происхождения. Вы можете настроить положение окна просмотра соответственно:

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

Так что на самом деле вы можете просто перемещаться вниз и вправо при увеличении, в зависимости от того, насколько вы увеличили масштаб относительно точки, в которой вы увеличивали.

enter image description here

51 голосов
/ 30 июня 2010

Наконец-то решил:

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.onwheel = 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.deltaY < 0 ? 1 : -1;

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

Ключ, как указано @ Tatarize , предназначен для вычисления положения оси таким образом, чтобы точка увеличения (указатель мыши) оставалась на том же месте после увеличения.

Первоначально мышь находится на расстоянии mouse/scale от угла, мы хотим, чтобы точка под мышкой оставалась на том же месте после увеличения, но это на mouse/new_scale от угла. Поэтому нам нужно сместить origin (координаты угла), чтобы учесть это.

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

Затем оставшийся код должен применить масштабирование и преобразовать его в контекст рисования, чтобы его происхождение совпадало с углом холста.

25 голосов
/ 27 мая 2010

Это на самом деле очень сложная задача (математически), и я почти работаю над тем же. Я задал аналогичный вопрос о Stackoverflow, но не получил ответа, но опубликовал в DocType (StackOverflow для HTML / CSS) и получил ответ. Проверьте это http://doctype.com/javascript-image-zoom-css3-transforms-calculate-origin-example

Я нахожусь в процессе создания плагина jQuery, который делает это (масштабирование в стиле Google Maps с использованием CSS3 Transforms). У меня немного работает масштабирование курсора мыши, все еще пытаюсь выяснить, как позволить пользователю перетаскивать холст вокруг, как вы можете это сделать в Google Maps. Когда я заработаю, я выложу код здесь, но посмотрите ссылку выше для части увеличения масштаба мыши.

Я не осознавал, что в контексте Canvas были методы масштабирования и перевода, вы можете добиться того же, используя CSS3, например. используя 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)');

Убедитесь, что в CSS3 transform-origin установлено значение 0, 0 (-moz-transform-origin: 0 0). Использование преобразования CSS3 позволяет вам увеличивать все, что угодно, просто убедитесь, что контейнер DIV настроен на переполнение: скрыто, чтобы остановить увеличение краев из сторон.

Независимо от того, используете ли вы CSS3-преобразования или собственные методы canvas и translate, все зависит от вас, но проверьте вышеуказанную ссылку для расчетов.


Обновление: Мех! Я просто опубликую код здесь, а не заставлю вас перейти по ссылке:

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

Вам, конечно, нужно будет адаптировать его для использования масштабирования холста и методов перевода.


Обновление 2: Только что заметил, что я использую transform-origin вместе с translate. Мне удалось реализовать версию, которая просто использует масштабирование и перевод самостоятельно, проверьте ее здесь http://www.dominicpettifer.co.uk/Files/Mosaic/MosaicTest.html Дождитесь загрузки изображений, затем используйте колесо мыши для масштабирования, также поддерживает панорамирование, перетаскивая изображение вокруг , Он использует CSS3 Transforms, но вы должны иметь возможность использовать те же вычисления для вашего Canvas.

7 голосов
/ 19 июля 2013

Я столкнулся с этой проблемой, используя c ++, чего мне, вероятно, не следовало бы использовать, просто для начала я использовал матрицы OpenGL ... в любом случае, если вы используете элемент управления, источником которого является верхний левый угол, и вы хотите панорамирование / масштабирование, как на картах Google, вот макет (с использованием allegro в качестве моего обработчика событий):

// 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; 
}
6 голосов
/ 23 мая 2017

Мне нравится Татаризатский ответ , но я предоставлю альтернативу.Это тривиальная задача линейной алгебры, и метод, который я представляю, хорошо работает с панорамированием, масштабированием, перекосом и т. Д. То есть он хорошо работает, если ваше изображение уже преобразовано.

Когда матрица масштабируется,шкала находится в точке (0, 0).Итак, если у вас есть изображение и вы масштабируете его в 2 раза, нижняя правая точка будет удваиваться в обоих направлениях (x и y) (согласно соглашению, что [0, 0] - это верхний левый угол изображения).

Если вместо этого вы хотите увеличить изображение относительно центра, то решение будет следующим: (1) переведите изображение так, чтобы его центр находился в (0, 0);(2) масштабировать изображение по x и y коэффициентам;(3) перевести изображение обратно.то есть

myMatrix
  .translate(image.width / 2, image.height / 2)    // 3
  .scale(xFactor, yFactor)                         // 2
  .translate(-image.width / 2, -image.height / 2); // 1

Более абстрактно, та же самая стратегия работает для любой точки.Если, например, вы хотите масштабировать изображение в точке P:

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y);

И, наконец, если изображение уже каким-либо образом трансформировано (например, если оно повернуто, перекошено, переведено илимасштабируется), то текущее преобразование необходимо сохранить.В частности, определенное выше преобразование необходимо умножить (или умножить вправо) на текущее преобразование.

myMatrix
  .translate(P.x, P.y)
  .scale(xFactor, yFactor)
  .translate(-P.x, -P.y)
  .multiply(myMatrix);

Вот оно.Вот план, который показывает это в действии.Прокрутите колесиком мыши по точкам, и вы увидите, что они постоянно остаются на месте.(Проверено только в Chrome.) http://plnkr.co/edit/3aqsWHPLlSXJ9JCcJzgH?p=preview

5 голосов
/ 16 марта 2016

Вот мое решение для центрированного изображения:

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>
3 голосов
/ 29 декабря 2013

Вот альтернативный способ сделать это, используя setTransform () вместо scale () и translate (). Все хранится в одном и том же объекте. Предполагается, что холст будет иметь 0,0 на странице, в противном случае вам нужно будет вычесть его положение из координат страницы.

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

Сопровождающий код для обработки панорамирования:

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

Чтобы получить ответ самостоятельно, учтите, что одинаковые координаты страницы должны совпадать с одинаковыми координатами холста до и после увеличения. Тогда вы можете сделать некоторую алгебру, начиная с этого уравнения:

(pageCoords - перевод) / scale = canvasCoords

3 голосов
/ 15 мая 2013

Я хочу разместить здесь некоторую информацию для тех, кто отдельно рисует и двигает рисунок - масштабирует его.

Это может быть полезно, если вы хотите сохранить масштаб и положение области просмотра.

Вот ящик:

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

Примечание * Масштаб 1008 * ДОЛЖЕН быть первым .

А вот зоомер:

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
    }

и, конечно, нам понадобится драггер:

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

    }


})
2 голосов
/ 27 октября 2016

Вот код реализации ответа @ tatarize с использованием PIXI.js. У меня есть окно просмотра, которое просматривает часть очень большого изображения (например, стиль Google Maps).

$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 - это мой HTML-контейнер.
  • imageContainer - это мой контейнер PIXI, в котором находится изображение.
  • mousePosOnImage - позиция мыши относительно всего изображения (не только порта просмотра).

Вот как я получил положение мыши:

  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 голосов
/ 30 апреля 2015
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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...