你如何放大特定点(没有canvas)?

How do you zoom into a specific point (no canvas)?

目标很简单,使用鼠标滚轮,放大特定点(鼠标所在的位置)。这意味着缩放后鼠标将位于图片的大致相同位置。

(纯属说明,不管你用的是海豚,鸭子还是圣母)

我不想使用 canvas,到目前为止我已经尝试过这样的操作:

HTML

<img src="whatever">

JS

function zoom(e){
    var deltaScale = deltaScale || -e.deltaY / 1000;
    var newScale = scale + deltaScale;
    var newWidth = img.naturalWidth * newScale;
    var newHeight = img.naturalHeight * newScale;
    var x = e.pageX;
    var y = e.pageY;
    var newX = x * newWidth / img.width;
    var newY = y * newHeight / img.height;
    var deltaX = newX - x;
    var deltaY = newY - y;
    setScale(newScale);
    setPosDelta(-deltaX,-deltaY);
}

function setPosDelta(dX, dY) {
    var imgPos = getPosition();
    setPosition(imgPos.x + dX, imgPos.y + dY);
}

function getPosition() {
    var x = parseFloat(img.style.left);
    var y = parseFloat(img.style.top);
    return {
        x: x,
        y: y
    }
}

function setScale(n) {
    scale = n;
    img.width = img.naturalWidth * n;
    img.height = img.naturalHeight * n;
}

这尝试做的是计算海豚眼睛的 x,y 坐标 before and after 缩放,并在计算出这两点之间的距离后,从 left,top 位置减去它以校正缩放位移,但没有特别成功。

缩放会自然地将图像向右和底部延伸,因此校正会尝试向左和向顶部拉回,以便将鼠标保持在该死的海豚眼上!但绝对不是。

告诉我,code/math 有什么问题?我觉得这个问题不是太宽泛,考虑到除了 canvas 之外我找不到任何解决方案。

谢谢!

[编辑] 重要

CSS 转换顺序很重要,如果您遵循所选答案,请确保先对转换进行排序,然后再对比例进行排序。 CSS 变换是反向执行的(从右到左 ),因此会先处理缩放,然后再处理平移。

这里是缩放到一个点的实现。该代码使用 CSS 2D transform 并包括在单击和拖动时平移图像。这很容易,因为比例没有变化。

缩放时的技巧是首先使用当前比例(换句话说:将其除以当前比例)对偏移量进行归一化,然后将新比例应用于该归一化偏移。这使光标准确地保持在独立于比例的位置。

var scale = 1,
    panning = false,
    xoff = 0,
    yoff = 0,
    start = {x: 0, y: 0},
    doc = document.getElementById("document");

function setTransform() {
  doc.style.transform = "translate(" + xoff + "px, " + yoff + "px) scale(" + scale + ")";
}

doc.onmousedown = function(e) {
  e.preventDefault();
  start = {x: e.clientX - xoff, y: e.clientY - yoff};    
  panning = true;
}

doc.onmouseup = function(e) {
  panning = false;
}

doc.onmousemove = function(e) {
  e.preventDefault();         
  if (!panning) {
    return;
  }
  xoff = (e.clientX - start.x);
  yoff = (e.clientY - start.y);
  setTransform();
}

doc.onwheel = function(e) {
    e.preventDefault();
    // take the scale into account with the offset
    var xs = (e.clientX - xoff) / scale,
        ys = (e.clientY - yoff) / scale,
        delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);

    // get scroll direction & set zoom level
    (delta > 0) ? (scale *= 1.2) : (scale /= 1.2);

    // reverse the offset amount with the new scale
    xoff = e.clientX - xs * scale;
    yoff = e.clientY - ys * scale;

    setTransform();          
}
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

#document {
  width: 100%;
  height: 100%;
  transform-origin: 0px 0px;
  transform: scale(1) translate(0px, 0px);
}
<div id="document">
  <img style="width: 100%"
       src="https://i.imgur.com/fHyEMsl.jpg"
       crossOrigin="" />
</div>

这是一个更接近您最初想法的实现,它使用顶部和左侧偏移并修改图像的宽度属性,而不是使用我的其他答案中的 css 转换。

var scale = 1.0,
  img = document.getElementById("image"),
  deltaX = 0,
  deltaY = 0;

// set the initial scale once the image is loaded
img.onload = function() {
  scale = image.offsetWidth / image.naturalWidth;
}

img.onwheel = function(e) {
  e.preventDefault();

  // first, remove the scale so we have the native offset
  var xoff = (e.clientX - deltaX) / scale,
    yoff = (e.clientY - deltaY) / scale,
    delta = (e.wheelDelta ? e.wheelDelta : -e.deltaY);

  // get scroll direction & set zoom level
  (delta > 0) ? (scale *= 1.05) : (scale /= 1.05);

  // limit the smallest size so the image does not disappear
  if (img.naturalWidth * scale < 16) {
    scale = 16 / img.naturalWidth;
  }

  // apply the new scale to the native offset
  deltaX = e.clientX - xoff * scale;
  deltaY = e.clientY - yoff * scale;

  // now modify the attributes of the image to reflect the changes
  img.style.top = deltaY + "px";
  img.style.left = deltaX + "px";

  img.style.width = (img.naturalWidth * scale) + "px";
}

window.onresize = function(e) {
  document.getElementById("wrapper").style.width = window.innerWidth + "px";
  document.getElementById("wrapper").style.height = window.innerHeight + "px";
}

window.onload = function(e) {
  document.getElementById("wrapper").style.width = window.innerWidth + "px";
  document.getElementById("wrapper").style.height = window.innerHeight + "px";
}
html,
body {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

div {
  overflow: hidden;
}
<div id="wrapper" style="position:relative;">
  <img id="image" style="width:100%;position:absolute;top:0px;left:0px;" 
   src="https://i.imgur.com/fHyEMsl.jpg"
   crossOrigin="" />
</div>

我喜欢 fmacdee 的两个帖子。我将他创建的代码分解为可在任何图像上调用的可重用版本。

只需调用:

var imageScaler = new ImageScaler(document.getElementById("image")); 
imageScaler.setup();

并将此代码包含在您项目的某处:

var ImageScaler = function(img)
{ 
    this.img = img; 
    this.scale = this.getImageScale(); 
    this.panning = false; 

    this.start = {x: 0, y: 0};
    this.delta = {x: 0, y: 0}; 
}; 

ImageScaler.prototype = 
{ 
    constructor: ImageScaler, 

    setup: function()
    { 
        this.setupEvents(); 
    }, 

    setupEvents: function()
    { 
        var img = this.img; 
        var callBack = this.onScale.bind(this); 
        var touchDown = this.touchDown.bind(this), 
        touhcMove = this.touchMove.bind(this), 
        touchUp = this.touchUp.bind(this); 

        img.onwheel = callBack; 
        img.onmousedown = touchDown;
        img.onmousemove = touhcMove;
        img.onmouseup = touchUp;
    }, 

    getImageScale: function()
    { 
        var img = this.img; 
        return img.offsetWidth / img.naturalWidth;
    }, 

    getMouseDirection: function(e)
    { 
        return (e.wheelDelta ? e.wheelDelta : -e.deltaY); 
    }, 

    getOffset: function(e)
    { 
        var scale = this.scale, 
        delta = this.delta; 
        // first, remove the scale so we have the native offset
        return {
            x: (e.clientX - delta.x) / scale, 
            y: (e.clientY - delta.y) / scale
        }; 
    }, 

    scaleElement: function(x, y, scale)
    { 
        var img = this.img; 
        img.style.top = y + "px";
        img.style.left = x + "px";
        img.style.width = (img.naturalWidth * scale) + "px";
    }, 

    minScale: 0.2, 

    updateScale: function(delta)
    { 
        // get scroll direction & set zoom level
        var scale = (delta > 0) ? (this.scale *= 1.05) : (this.scale /= 1.05);

        // limit the smallest size so the image does not disappear
        if (scale <= this.minScale) 
        {
            this.scale = this.minScale;
        }
        return this.scale; 
    }, 

    touchDown: function(e)
    { 
        var delta = this.delta; 
        this.start = {x: e.clientX - delta.x, y: e.clientY - delta.y};
        this.panning = true; 
    }, 

    touchMove: function(e)
    { 
        e.preventDefault();         
        if (this.panning === false) 
        {
            return;
        } 

        var delta = this.delta, 
        start = this.start; 
        delta.x = (e.clientX - start.x);
        delta.y = (e.clientY - start.y); 
        console.log(delta, start)

        this.scaleElement(delta.x, delta.y, this.scale);
    }, 

    touchUp: function(e)
    { 
        this.panning = false; 
    }, 

    onScale: function(e)
    { 
        var offset = this.getOffset(e); 
        e.preventDefault(); 

        // get scroll direction & set zoom level
        var delta = this.getMouseDirection(e);
        var scale = this.updateScale(delta); 

        // apply the new scale to the native offset
        delta = this.delta; 
        delta.x = e.clientX - offset.x * scale;
        delta.y = e.clientY - offset.y * scale;

        this.scaleElement(delta.x, delta.y, scale); 
    }
};

我做了一个fiddle查看结果:http://jsfiddle.net/acqo5n8s/12/