草庐IT

javascript - 缩放后使用 Translate 将元素定位在另一个元素之上

coder 2024-07-16 原文

我正在实现一个基于 Croppie 的图像裁剪工具.我的问题是,当我缩小得太远时,图像会离开视口(viewport)或离裁剪区域太远。

所以我有一个函数,在线(下面的代码片段)363 是这样的:

function _updateCenterPoint() { //borked
  console.log("fire _updateCenterPoint()");
  var self = this,
    transform = Transform.parse(self.childElements.img.style[CSS_TRANSFORM]);
  var imgRect = self.childElements.img.getBoundingClientRect(),
    cropRect = self.childElements.enclosedCrop.getBoundingClientRect();
  if (!isContainedByCrop(imgRect, cropRect, transform)) {
    console.log("adjust center");
    transform.x = //do a transform (I've tried many - see below)
    transform.y = //do a transform (I've tried many - see below);
  }
  var newCss = {};
  newCss['transform'] = transform.toString();
  css(self.childElements.img, newCss);

}

在元素缩小太多后定位元素由 if (!isContainedByCrop(imgRect, cropRect, transform)) 条件逻辑处理,其中 “调整中心” 已记录。

我已经尝试了很多方法来做到这一点。我尝试使用距离公式,但感到困惑,因为我比较的是翻译后的值而不是 (x,y) 点。基本上,一旦缩小到一定数量以下,它就应该适合裁剪工具。

但是,我无法使其行为一致。当我将它缩小到 150 x 150 像素时,它有时会在裁剪工具中居中,有时不会。

缩放增加了复杂性(对我而言)。如果你不缩放它,这将是一个简单的等式,只需通过 .enclosedCropgetBoundingClientRect().top(白色居中圆圈)减去图像的 getBoundingClientRect().top(x 值相同),它会到达它应该去的地方。但是因为我正在缩放它,所以它的位置在变化,我不知道如何解释它。

它在过去也有效(取决于我确定的方程式),前提是我没有通过在特定方向拖动鼠标来定位图像。无论我如何平移/缩放图像,方程式都应该有效。

缩放会像这样改变元素的位置:

MDN : scale()

这是一个距离(类似)公式的尝试:

transform.x = Math.sqrt( (cropRect.left - self.imgRectLeftOrig) * (cropRect.left - self.imgRectLeftOrig) - (imgRect.left * transform.scale) * (imgRect.left * transform.scale));
transform.y = Math.sqrt( (cropRect.top - self.imgRectTopOrig) * (cropRect.top - self.imgRectTopOrig) -  (imgRect.top * transform.scale) * (imgRect.top * transform.scale));

它不起作用,因为我没有尝试获取两点之间的距离。相反,我试图沿 x 轴和 y 轴适本地平移,以将图像定位在麦田圈的顶部。当我尝试从 left 减去 left 和从 top 减去 top 时,这似乎也不起作用。

鉴于这两个图表,我不明白为什么:

transform.x = cropRect.left - (imgRect.left * transform.scale);
transform.y = cropRect.top - (imgRect.top * transform.scale);

不工作...

此外,如果我有一个图像,并且我使用 css("transform","scale(1.1) 对其进行缩放,它似乎表现在 .getBoundingClientRect().top 值下降,而不是上升,这让 MDN 图更让我困惑,据我所知,它们显示的是相反的情况。也许是 CSS 问题?

function initAngularCrop() {
  var self = this;
  self.data = {};
  self.childElements = {};
  self.type = type = 'small';
  viewBox = self.childElements.viewBox = document.createElement('div');
  enclosedCrop = self.childElements.enclosedCrop = document.createElement('div');
  img = self.childElements.img = document.createElement('img');
  overlay = self.childElements.overlay = document.createElement('div');
  viewBox.className = viewBox.className ? 'viewBox' + ' ' + viewBox.className : 'viewBox';
  enclosedCrop.className = enclosedCrop.className ? 'enclosedCrop' + ' ' + enclosedCrop.className : 'enclosedCrop';
  enclosedCrop.className = enclosedCrop.className ? enclosedCrop.className + ' ' + type : type;
  overlay.className = overlay.className ? 'overlay' + ' ' + overlay.className : 'overlay';
  overlay.className = overlay.className ? type + ' ' + overlay.className : type;
  img.className = img.className ? 'bigTuna' + ' ' + img.className : 'bigTuna';
  img.src = "https://images.genius.com/2774bb81e57abc5c808b50c45eaa75f2.600x600x1.jpg";
  document.body.appendChild(viewBox);
  viewBox.appendChild(img);
  viewBox.appendChild(enclosedCrop);
  viewBox.appendChild(overlay);
  initDraggable.call(self);
  zoomzoomwrap = self.childElements.zoomzoomwrap = document.createElement('div'),
    zoomzoom = self.childElements.zoomzoom = document.createElement('input');
  zoomzoomwrap.className = zoomzoomwrap.className ? 'zoomzoomboomboom' + ' ' + zoomzoomwrap.className : 'zoomzoomboomboom';
  zoomzoom.className = zoomzoom.className ? 'littleTuna' + ' ' + zoomzoom.className : 'littleTuna';
  zoomzoom.type = 'range';
  zoomzoom.step = '0.0001';
  zoomzoom.value = 5;
  zoomzoom.style.display = '';
  zoomzoom.setAttribute('aria-label', 'zoom');
  zoomzoom.min = 0;
  zoomzoom.max = 10;
  viewBox.parentNode.insertBefore(zoomzoomwrap, viewBox.nextSibling);
  zoomzoomwrap.appendChild(zoomzoom);
  self._currentZoom = 1;
  self.imgRectTopOrig = img.getBoundingClientRect().top;
  self.imgRectLeftOrig = img.getBoundingClientRect().left;

  function change() {
    initZoom.call(self, {
      value: parseFloat(zoomzoom.value / 5),
      origin: new TransformOrigin(img),
      viewportRect: enclosedCrop.getBoundingClientRect(),
      transform: Transform.parse(img)
    });
  }
  self.childElements.zoomzoom.addEventListener('input', change);
  //self.childElements.zoomzoom.addEventListener('change', change);
  //console.log(self.childElements.zoomzoom);
}

var _debouncetimer;
var _debounce = function(fn, delay, context) {
  //console.log("debounce run",fn);
  clearTimeout(_debouncetimer);
  _debouncetimer = setTimeout(function() {
    fn.call(context);
    //console.log("debounce fire!");
  }, delay);
};

function initZoom(ui) {
  var self = this,
    transform = ui ? ui.transform : Transform.parse(self.childElements.img),
    vpRect = ui ? ui.viewportRect : self.childElements.viewport.getBoundingClientRect(),
    origin = ui ? ui.origin : new TransformOrigin(self.childElements.img);

  function applyCss(bar) {
    if (self.type == "small") {
      var size = 150;
    }
    //console.log("apply zoom CSS");
    if (bar) {
      if (transform.scale * self.childElements.img.width > size && transform.scale * self.childElements.img.height > size) {
        var transCss = {};
        transCss[CSS_TRANSFORM] = transform.toString();
        css(self.childElements.img, transCss);
      } else {
        if (self.childElements.img.width > self.childElements.img.height) {
          transform.scale = size / self.childElements.img.width;
        } else {
          transform.scale = size / self.childElements.img.height;
        }
        var transCss = {};
        transCss[CSS_TRANSFORM] = transform.toString();
        css(self.childElements.img, transCss);
      }
      _debounce(_updateCenterPoint, 500, self);
    } else {
      _debounce(_updateCenterPoint, 500, self);
    }
  }
  if (self.type == "small") {
    if ((self.childElements.img.getBoundingClientRect().width <= 150 || self.childElements.img.getBoundingClientRect().height <= 150) && (ui.value < 1 && ui.value < self._currentZoom)) {
      applyCss(false);
      return;
    } else {
      self._currentZoom = ui ? ui.value : self._currentZoom;
      transform.scale = self._currentZoom;
      self.childElements.zoomzoom.setAttribute('aria-valuenow', self._currentZoom);
      applyCss(true);
      return;
    }
  }
  applyCss(); //othersizes

  /* _debouncedOverlay.call(self);
   _triggerUpdate.call(self);*/
}

function _getVirtualBoundaries(viewport) {
  var self = this,
    scale = self._currentZoom,
    vpWidth = viewport.width,
    vpHeight = viewport.height,
    centerFromBoundaryX = self.childElements.viewBox.clientWidth / 2,
    centerFromBoundaryY = self.childElements.viewBox.clientHeight / 2,
    imgRect = self.childElements.img.getBoundingClientRect(),
    curImgWidth = imgRect.width,
    curImgHeight = imgRect.height,
    halfWidth = vpWidth / 2,
    halfHeight = vpHeight / 2;

  var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1;
  var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale)));

  var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1;
  var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale)));

  var originMinX = (1 / scale) * halfWidth;
  var originMaxX = (curImgWidth * (1 / scale)) - originMinX;

  var originMinY = (1 / scale) * halfHeight;
  var originMaxY = (curImgHeight * (1 / scale)) - originMinY;

  return {
    translate: {
      maxX: maxX,
      minX: minX,
      maxY: maxY,
      minY: minY
    },
    origin: {
      maxX: originMaxX,
      minX: originMinX,
      maxY: originMaxY,
      minY: originMinY
    }
  };
}

function initDraggable() {
  var self = this,
    isDragging = false,
    originalX,
    originalY,
    originalDistance,
    vpRect,
    transform;

  function assignTransformCoordinates(deltaX, deltaY) {
    var imgRect = self.childElements.img.getBoundingClientRect(),
      top = transform.y + deltaY,
      left = transform.x + deltaX;
    if (vpRect.top + 15 > imgRect.top + deltaY && vpRect.bottom < 15 + imgRect.bottom + deltaY) {
      transform.y = top;
      //console.log("shift!");
    }

    if (vpRect.left + 15 > imgRect.left + deltaX && vpRect.right < 15 + imgRect.right + deltaX) {
      transform.x = left;
      //console.log("shift!");
    }
  }

  function mouseDown(ev) {
    if (ev.button !== undefined && ev.button !== 0) return;

    ev.preventDefault();
    if (isDragging) return;
    isDragging = true;
    originalX = ev.pageX;
    originalY = ev.pageY;

    if (ev.touches) {
      var touches = ev.touches[0];
      originalX = touches.pageX;
      originalY = touches.pageY;
    }

    transform = Transform.parse(self.childElements.img); //key point
    window.addEventListener('mousemove', mouseMove);
    window.addEventListener('touchmove', mouseMove);
    window.addEventListener('mouseup', mouseUp);
    window.addEventListener('touchend', mouseUp);
    document.body.style['userSelect'] = 'none';
    vpRect = self.childElements.enclosedCrop.getBoundingClientRect();
  }

  function mouseMove(ev) {
    //shift it around with mouse/touch
    ev.preventDefault();
    var pageX = ev.pageX,
      pageY = ev.pageY;

    if (ev.touches) {
      var touches = ev.touches[0];
      pageX = touches.pageX;
      pageY = touches.pageY;
    }

    var deltaX = pageX - originalX,
      deltaY = pageY - originalY,
      newCss = {};

    if (ev.type == 'touchmove') {
      if (ev.touches.length > 1) {
        var touch1 = ev.touches[0];
        var touch2 = ev.touches[1];
        var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY));
        //above is math :(
        if (!originalDistance) {
          originalDistance = dist / self._currentZoom;
        }

        var scale = dist / originalDistance;

        _setZoomerVal.call(self, scale);
        dispatchChange(self.childElements.zoomzoom);
        return; //why
      }
    }

    assignTransformCoordinates(deltaX, deltaY);
    //console.log(pageX,originalX,pageY,originalY,deltaX,deltaY);
    newCss['transform'] = transform.toString();
    //console.log(newCss,transform.toString());
    css(self.childElements.img, newCss);
    //_updateOverlay.call(self);
    originalY = pageY;
    originalX = pageX;
  }

  function mouseUp() {
    isDragging = false;
    window.removeEventListener('mousemove', mouseMove);
    window.removeEventListener('touchmove', mouseMove);
    window.removeEventListener('mouseup', mouseUp);
    window.removeEventListener('touchend', mouseUp);
    document.body.style['userSelect'] = '';
    //_updateCenterPoint.call(self);
    //_triggerUpdate.call(self);
    originalDistance = 0;
  }

  self.childElements.overlay.addEventListener('mousedown', mouseDown);
  self.childElements.overlay.addEventListener('touchstart', mouseDown);
} //initDraggable()

var TRANSLATE_OPTS = {
  'translate3d': {
    suffix: ', 0px'
  },
  'translate': {
    suffix: ''
  }
};
var Transform = function(x, y, scale) {
  this.x = parseFloat(x);
  this.y = parseFloat(y);
  this.scale = parseFloat(scale);
};

Transform.parse = function(v) {
  if (v.style) {
    return Transform.parse(v.style['transform']);
  } else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) {
    return Transform.fromMatrix(v);
  } else {
    return Transform.fromString(v);
  }
};
Transform.fromMatrix = function(v) {
  var vals = v.substring(7).split(',');
  if (!vals.length || v === 'none') {
    vals = [1, 0, 0, 1, 0, 0];
  }

  return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0]));
};

Transform.fromString = function(v) {
  var values = v.split(') '),
    translate = values[0].substring("translate3d".length + 1).split(','),
    scale = values.length > 1 ? values[1].substring(6) : 1,
    x = translate.length > 1 ? translate[0] : 0,
    y = translate.length > 1 ? translate[1] : 0;

  return new Transform(x, y, scale);
};
Transform.prototype.toString = function() {
  var suffix = TRANSLATE_OPTS["translate3d"].suffix || '';
  return "translate3d" + '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')';
};
var TransformOrigin = function(el) {
  //console.log(el.style[CSS_TRANS_ORG] + " Transform Origin");
  if (!el || !el.style[CSS_TRANS_ORG]) {
    this.x = 0;
    this.y = 0;
    return;
  }
  var css = el.style[CSS_TRANS_ORG].split(' ');
  this.x = parseFloat(css[0]);
  this.y = parseFloat(css[1]);
};

TransformOrigin.prototype.toString = function() {
  return this.x + 'px ' + this.y + 'px';
};

function css(el, styles, val) {
  if (typeof(styles) === 'string') {
    var tmp = styles;
    styles = {};
    styles[tmp] = val;
  }

  for (var prop in styles) {
    el.style[prop] = styles[prop];
  }
}

function isContainedByCrop(imgRect, cropRect, transform) {
  if ((imgRect.top > cropRect.top || imgRect.bottom < cropRect.bottom) || (imgRect.left > cropRect.left || imgRect.right < cropRect.right)) {
    return false;
  } else {
    return true; //contained 
  }
}

var emptyStyles = document.createElement('div').style;
var cssPrefixes = ['Webkit', 'Moz', 'ms'];

function vendorPrefix(prop) {
  if (prop in emptyStyles) {
    return prop;
  }

  var capProp = prop[0].toUpperCase() + prop.slice(1),
    i = cssPrefixes.length;

  while (i--) {
    prop = cssPrefixes[i] + capProp;
    if (prop in emptyStyles) {
      return prop;
    }
  }
}

var CSS_TRANSFORM = vendorPrefix('transform');
var CSS_TRANS_ORG = vendorPrefix('transformOrigin');

function _updateCenterPoint() { //borked
  console.log("fire _updateCenterPoint()");
  var self = this,
    transform = Transform.parse(self.childElements.img.style[CSS_TRANSFORM]);
  var imgRect = self.childElements.img.getBoundingClientRect(),
    cropRect = self.childElements.enclosedCrop.getBoundingClientRect();
  if (!isContainedByCrop(imgRect, cropRect, transform)) {
    console.log("adjust center");
    transform.x = self.imgRectLeftOrig - (cropRect.left - imgRect.left - (transform.x * transform.scale));
    //console.log(self.childElements.enclosedCrop.offsetLeft,cropRect.left,self.childElements.img.offsetLeft,imgRect.left);
    transform.y = self.imgRectTopOrig - (cropRect.top - imgRect.top - (transform.y * transform.scale));
  }
  var newCss = {};
  //newCss['transformOrigin'] = center.x + 'px ' + center.y + 'px';
  newCss['transform'] = transform.toString();
  css(self.childElements.img, newCss);

}

new initAngularCrop();
.viewBox {
  position: relative;
  overflow: hidden;
  display: inline-block;
  margin: 0 auto;
  width: 500px;
  height: 500;
}

.enclosedCrop {
  position: absolute;
  width: 150px;
  height: 150px;
  margin: auto;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border: 2px solid #fff;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 100%;
}

.overlay.small,
.overlay.large {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

最佳答案

transform 应用于原始位置,因此将transform 考虑在内以调整transform 的计算必然很复杂.您必须考虑变换的顺序、原始位置和变换原点。

例如,在这里,一个向左平移 100 像素并缩放为 0.5 的正方形将位于 125 像素的位置。首先它移动到 100px,然后从中心缩放到 125px。如果你以前缩放过,那么你是 75px。从中心缩放到 50 像素(所以在左边 25 像素),然后将 100 像素缩放到 50 像素。所以获得正确的坐标可能很复杂。

body {
  margin: 0px;
}

#red {
  transform: scale(0.5) translate(100px, 100px);
}

#blue {
  transform: translate(100px, 100px) scale(0.5);
}

table {
  border-collapse: collapse;
}

td {
  height: 47px;
  width: 47px;
  border: solid 1px #aaa;
}
<div id="blue" style="position: absolute; width: 100px; height: 100px; background-color: blue;">

</div>

<div id="red"  style="position: absolute; width: 100px; height: 100px; background-color: red;">

</div>

<table>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td></td><td></td></tr>

</table>

我认为,对于您的情况,更简单的方法是使用 transform-origin。您的 updateCenter 函数正是 transform-origin 所做的,它定义了中心。它是内置的。所有计算都将由转换处理。您只需要定义所需的原点,这非常简单。

假设您希望圆心成为示例中圆心的点。你需要找到这个点在你转换的图像上的百分比。所以这基本上是

(half the width of the circle + left offset of the circle - left offset of the image) / (width of image) * 100

在你的情况下给出:

 (cropRect.left + (cropRect.width / 2) - matrix.m41) / self.childElements.img.offsetWidth * 100;

matrix.41 是图像的左侧翻译,您可以通过这种方式找到它(请参阅此答案:How to get value translateX by javascript):

  var transform = window.getComputedStyle(self.childElements.img).transform;
  var matrix = new WebKitCSSMatrix(transform);

然后你分配:

self.childElements.img.style.transformOrigin = transformOriginX + "% " + transformOriginY + "%";

显然,您只需要在移动图像时执行此操作,而不是在缩放时执行此操作,因为那时原点已经被处理了。您也可以仅在特定条件下进行调整,但无论如何,您无需考虑平移和缩放。

function initAngularCrop() {
  var self = this;
  self.data = {};
  self.childElements = {};
  self.type = type = 'small';
  viewBox = self.childElements.viewBox = document.createElement('div');
  enclosedCrop = self.childElements.enclosedCrop = document.createElement('div');
  img = self.childElements.img = document.createElement('img');
  overlay = self.childElements.overlay = document.createElement('div');
  viewBox.className = viewBox.className ? 'viewBox' + ' ' + viewBox.className : 'viewBox';
  enclosedCrop.className = enclosedCrop.className ? 'enclosedCrop' + ' ' + enclosedCrop.className : 'enclosedCrop';
  enclosedCrop.className = enclosedCrop.className ? enclosedCrop.className + ' ' + type : type;
  overlay.className = overlay.className ? 'overlay' + ' ' + overlay.className : 'overlay';
  overlay.className = overlay.className ? type + ' ' + overlay.className : type;
  img.className = img.className ? 'bigTuna' + ' ' + img.className : 'bigTuna';
  img.src = "https://images.genius.com/2774bb81e57abc5c808b50c45eaa75f2.600x600x1.jpg";
  document.body.appendChild(viewBox);
  viewBox.appendChild(img);
  viewBox.appendChild(enclosedCrop);
  viewBox.appendChild(overlay);
  initDraggable.call(self);
  zoomzoomwrap = self.childElements.zoomzoomwrap = document.createElement('div'),
    zoomzoom = self.childElements.zoomzoom = document.createElement('input');
  zoomzoomwrap.className = zoomzoomwrap.className ? 'zoomzoomboomboom' + ' ' + zoomzoomwrap.className : 'zoomzoomboomboom';
  zoomzoom.className = zoomzoom.className ? 'littleTuna' + ' ' + zoomzoom.className : 'littleTuna';
  zoomzoom.type = 'range';
  zoomzoom.step = '0.0001';
  zoomzoom.value = 5;
  zoomzoom.style.display = '';
  zoomzoom.setAttribute('aria-label', 'zoom');
  zoomzoom.min = 0;
  zoomzoom.max = 10;
  viewBox.parentNode.insertBefore(zoomzoomwrap, viewBox.nextSibling);
  zoomzoomwrap.appendChild(zoomzoom);
  self._currentZoom = 1;
  self.imgRectTopOrig = img.getBoundingClientRect().top;
  self.imgRectLeftOrig = img.getBoundingClientRect().left;

  function change() {
    initZoom.call(self, {
      value: parseFloat(zoomzoom.value / 5),
      origin: new TransformOrigin(img),
      viewportRect: enclosedCrop.getBoundingClientRect(),
      transform: Transform.parse(img)
    });
  }
  self.childElements.zoomzoom.addEventListener('input', change);
  //self.childElements.zoomzoom.addEventListener('change', change);
  //console.log(self.childElements.zoomzoom);
}

var _debouncetimer;
var _debounce = function(fn, delay, context) {
  //console.log("debounce run",fn);
  clearTimeout(_debouncetimer);
  _debouncetimer = setTimeout(function() {
    fn.call(context);
    //console.log("debounce fire!");
  }, delay);
};

function initZoom(ui) {
  var self = this,
    transform = ui ? ui.transform : Transform.parse(self.childElements.img),
    vpRect = ui ? ui.viewportRect : self.childElements.viewport.getBoundingClientRect(),
    origin = ui ? ui.origin : new TransformOrigin(self.childElements.img);

  function applyCss(bar) {
    if (self.type == "small") {
      var size = 150;
    }
    //console.log("apply zoom CSS");
    if (bar) {
      if (transform.scale * self.childElements.img.width > size && transform.scale * self.childElements.img.height > size) {
        var transCss = {};
        transCss[CSS_TRANSFORM] = transform.toString();
        css(self.childElements.img, transCss);
      } else {
        if (self.childElements.img.width > self.childElements.img.height) {
          transform.scale = size / self.childElements.img.width;
        } else {
          transform.scale = size / self.childElements.img.height;
        }
        var transCss = {};
        transCss[CSS_TRANSFORM] = transform.toString();
        css(self.childElements.img, transCss);
      }
      _debounce(_updateCenterPoint, 500, self);
    } else {
      _debounce(_updateCenterPoint, 500, self);
    }
  }
  if (self.type == "small") {
    if ((self.childElements.img.getBoundingClientRect().width <= 150 || self.childElements.img.getBoundingClientRect().height <= 150) && (ui.value < 1 && ui.value < self._currentZoom)) {
      applyCss(false);
      return;
    } else {
      self._currentZoom = ui ? ui.value : self._currentZoom;
      transform.scale = self._currentZoom;
      self.childElements.zoomzoom.setAttribute('aria-valuenow', self._currentZoom);
      applyCss(true);
      return;
    }
  }
  applyCss(); //othersizes

  /* _debouncedOverlay.call(self);
   _triggerUpdate.call(self);*/
}

function _getVirtualBoundaries(viewport) {
  var self = this,
    scale = self._currentZoom,
    vpWidth = viewport.width,
    vpHeight = viewport.height,
    centerFromBoundaryX = self.childElements.viewBox.clientWidth / 2,
    centerFromBoundaryY = self.childElements.viewBox.clientHeight / 2,
    imgRect = self.childElements.img.getBoundingClientRect(),
    curImgWidth = imgRect.width,
    curImgHeight = imgRect.height,
    halfWidth = vpWidth / 2,
    halfHeight = vpHeight / 2;

  var maxX = ((halfWidth / scale) - centerFromBoundaryX) * -1;
  var minX = maxX - ((curImgWidth * (1 / scale)) - (vpWidth * (1 / scale)));

  var maxY = ((halfHeight / scale) - centerFromBoundaryY) * -1;
  var minY = maxY - ((curImgHeight * (1 / scale)) - (vpHeight * (1 / scale)));

  var originMinX = (1 / scale) * halfWidth;
  var originMaxX = (curImgWidth * (1 / scale)) - originMinX;

  var originMinY = (1 / scale) * halfHeight;
  var originMaxY = (curImgHeight * (1 / scale)) - originMinY;

  return {
    translate: {
      maxX: maxX,
      minX: minX,
      maxY: maxY,
      minY: minY
    },
    origin: {
      maxX: originMaxX,
      minX: originMinX,
      maxY: originMaxY,
      minY: originMinY
    }
  };
}

function initDraggable() {
  var self = this,
    isDragging = false,
    originalX,
    originalY,
    originalDistance,
    vpRect,
    transform;

  function assignTransformCoordinates(deltaX, deltaY) {
    var imgRect = self.childElements.img.getBoundingClientRect(),
      top = transform.y + deltaY,
      left = transform.x + deltaX;
    if (vpRect.top + 15 > imgRect.top + deltaY && vpRect.bottom < 15 + imgRect.bottom + deltaY) {
      transform.y = top;
      //console.log("shift!");
    }

    if (vpRect.left + 15 > imgRect.left + deltaX && vpRect.right < 15 + imgRect.right + deltaX) {
      transform.x = left;
      //console.log("shift!");
    }
  }

  function mouseDown(ev) {
    if (ev.button !== undefined && ev.button !== 0) return;

    ev.preventDefault();
    if (isDragging) return;
    isDragging = true;
    originalX = ev.pageX;
    originalY = ev.pageY;

    if (ev.touches) {
      var touches = ev.touches[0];
      originalX = touches.pageX;
      originalY = touches.pageY;
    }

    transform = Transform.parse(self.childElements.img); //key point
    window.addEventListener('mousemove', mouseMove);
    window.addEventListener('touchmove', mouseMove);
    window.addEventListener('mouseup', mouseUp);
    window.addEventListener('touchend', mouseUp);
    document.body.style['userSelect'] = 'none';
    vpRect = self.childElements.enclosedCrop.getBoundingClientRect();
  }

  function mouseMove(ev) {
    //shift it around with mouse/touch
    ev.preventDefault();
    var pageX = ev.pageX,
      pageY = ev.pageY;

    if (ev.touches) {
      var touches = ev.touches[0];
      pageX = touches.pageX;
      pageY = touches.pageY;
    }

    var deltaX = pageX - originalX,
      deltaY = pageY - originalY,
      newCss = {};

    if (ev.type == 'touchmove') {
      if (ev.touches.length > 1) {
        var touch1 = ev.touches[0];
        var touch2 = ev.touches[1];
        var dist = Math.sqrt((touch1.pageX - touch2.pageX) * (touch1.pageX - touch2.pageX) + (touch1.pageY - touch2.pageY) * (touch1.pageY - touch2.pageY));
        //above is math :(
        if (!originalDistance) {
          originalDistance = dist / self._currentZoom;
        }

        var scale = dist / originalDistance;

        _setZoomerVal.call(self, scale);
        dispatchChange(self.childElements.zoomzoom);
        return; //why
      }
    }

    assignTransformCoordinates(deltaX, deltaY);
    //console.log(pageX,originalX,pageY,originalY,deltaX,deltaY);
    newCss['transform'] = transform.toString();
    //console.log(newCss,transform.toString());
    css(self.childElements.img, newCss);
    //_updateOverlay.call(self);
    originalY = pageY;
    originalX = pageX;
    _updateCenterPoint.call(self);
  }

  function mouseUp() {
    isDragging = false;
    window.removeEventListener('mousemove', mouseMove);
    window.removeEventListener('touchmove', mouseMove);
    window.removeEventListener('mouseup', mouseUp);
    window.removeEventListener('touchend', mouseUp);
    document.body.style['userSelect'] = '';

    //_triggerUpdate.call(self);
    originalDistance = 0;


  }

  self.childElements.overlay.addEventListener('mousedown', mouseDown);
  self.childElements.overlay.addEventListener('touchstart', mouseDown);

} //initDraggable()

var TRANSLATE_OPTS = {
  'translate3d': {
    suffix: ', 0px'
  },
  'translate': {
    suffix: ''
  }
};
var Transform = function(x, y, scale) {
  this.x = parseFloat(x);
  this.y = parseFloat(y);
  this.scale = parseFloat(scale);
};

Transform.parse = function(v) {
  if (v.style) {
    return Transform.parse(v.style['transform']);
  } else if (v.indexOf('matrix') > -1 || v.indexOf('none') > -1) {
    return Transform.fromMatrix(v);
  } else {
    return Transform.fromString(v);
  }
};
Transform.fromMatrix = function(v) {
  var vals = v.substring(7).split(',');
  if (!vals.length || v === 'none') {
    vals = [1, 0, 0, 1, 0, 0];
  }

  return new Transform(num(vals[4]), num(vals[5]), parseFloat(vals[0]));
};

Transform.fromString = function(v) {
  var values = v.split(') '),
    translate = values[0].substring("translate3d".length + 1).split(','),
    scale = values.length > 1 ? values[1].substring(6) : 1,
    x = translate.length > 1 ? translate[0] : 0,
    y = translate.length > 1 ? translate[1] : 0;

  return new Transform(x, y, scale);
};
Transform.prototype.toString = function() {
  var suffix = TRANSLATE_OPTS["translate3d"].suffix || '';
  return "translate3d" + '(' + this.x + 'px, ' + this.y + 'px' + suffix + ') scale(' + this.scale + ')';
};
var TransformOrigin = function(el) {
  //console.log(el.style[CSS_TRANS_ORG] + " Transform Origin");
  if (!el || !el.style[CSS_TRANS_ORG]) {
    this.x = 0;
    this.y = 0;
    return;
  }
  var css = el.style[CSS_TRANS_ORG].split(' ');
  this.x = parseFloat(css[0]);
  this.y = parseFloat(css[1]);
};

TransformOrigin.prototype.toString = function() {
  return this.x + 'px ' + this.y + 'px';
};

function css(el, styles, val) {
  if (typeof(styles) === 'string') {
    var tmp = styles;
    styles = {};
    styles[tmp] = val;
  }

  for (var prop in styles) {
    el.style[prop] = styles[prop];
  }
}

function isContainedByCrop(imgRect, cropRect, transform) {
  if ((imgRect.top > cropRect.top || imgRect.bottom < cropRect.bottom) || (imgRect.left > cropRect.left || imgRect.right < cropRect.right)) {
    return false;
  } else {
    return true; //contained 
  }
}

var emptyStyles = document.createElement('div').style;
var cssPrefixes = ['Webkit', 'Moz', 'ms'];

function vendorPrefix(prop) {
  if (prop in emptyStyles) {
    return prop;
  }

  var capProp = prop[0].toUpperCase() + prop.slice(1),
    i = cssPrefixes.length;

  while (i--) {
    prop = cssPrefixes[i] + capProp;
    if (prop in emptyStyles) {
      return prop;
    }
  }
}

var CSS_TRANSFORM = vendorPrefix('transform');
var CSS_TRANS_ORG = vendorPrefix('transformOrigin');

function _updateCenterPoint() { //borked
  //console.log("fire _updateCenterPoint()");
  var self = this;
  var imgRect = self.childElements.img.getBoundingClientRect(),
    cropRect = self.childElements.enclosedCrop.getBoundingClientRect();
  var transform = window.getComputedStyle(self.childElements.img).transform;
  var matrix = new WebKitCSSMatrix(transform);
  var transformOriginX = (cropRect.left + (cropRect.width / 2) - matrix.m41) / self.childElements.img.offsetWidth * 100;
  var transformOriginY = (self.childElements.enclosedCrop.offsetTop + (cropRect.height / 2) - matrix.m42) / self.childElements.img.offsetHeight * 100;

  self.childElements.img.style.transformOrigin = transformOriginX + "% " + transformOriginY + "%";

}

new initAngularCrop();
body {
  margin: 0px;
}

.viewBox {
  position: relative;
  overflow: hidden;
  display: inline-block;
  margin: 0 auto;
  width: 500px;
  height: 500;
}

.enclosedCrop {
  position: absolute;
  width: 150px;
  height: 150px;
  margin: auto;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border: 2px solid #fff;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 100%;
}

.overlay.small,
.overlay.large {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

关于javascript - 缩放后使用 Translate 将元素定位在另一个元素之上,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48230640/

有关javascript - 缩放后使用 Translate 将元素定位在另一个元素之上的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐