Elemento de posição na parte superior de outro usando o Google Translate após ter sido dimensionado

9

Estou implementando uma ferramenta de corte de imagem baseada em Croppie . Meu problema é que, quando eu diminuo o zoom, a imagem deixa a janela de visualização ou está muito longe da área de corte.

Então eu tenho uma função, on line (trecho de código abaixo) 363 que é assim:

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

}

Posicionar o elemento depois que ele diminui demais é tratado pela lógica dentro da if (!isContainedByCrop(imgRect, cropRect, transform)) condicional, onde "adjust center" é registrado.

Eu tentei várias maneiras de fazer isso. Eu tentei usar a fórmula de distância, mas fiquei confuso porque estou comparando valores traduzidos não (x, y) pontos. Basicamente, ele deve caber dentro da ferramenta de recorte, uma vez que encolhe abaixo de uma certa quantia.

No entanto, não consigo fazer com que ele se comporte de forma consistente. Quando diminuo para 150 por 150 pixels, às vezes ele fica centrado na ferramenta de recorte, às vezes não.

O escalonamento aumenta a complexidade (para mim). Se você não estivesse escalando, seria uma equação fácil, basta traduzir .enclosedCrop (o círculo centrado em branco) da getBoundingClientRect().top menos a getBoundingClientRect().top da imagem (o mesmo para os valores x) e seria onde é suposto. Mas porque estou escalando, a localização está mudando e não sei como explicar isso.

Ele também funcionou no passado (dependendo de qual equação eu definir) apenas se eu não posicionar a imagem arrastando o mouse em certas direções. A equação deve funcionar, não importa como eu traduza / dimensione a imagem.

O dimensionamento altera a localização de um elemento da seguinte forma:

MDN: scale ()

Aqui está uma tentativa com uma fórmula de distância (como):

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

Não funciona porque não estou tentando obter a distância entre dois pontos. Em vez disso, estou tentando traduzir ao longo dos eixos xe y, conforme apropriado, para posicionar a imagem na parte superior do círculo de corte. Quando tento apenas subtrair a esquerda da esquerda e a parte superior da parte superior, isso também não parece funcionar.

Dadosessesdoisgráficos,nãoentendoporque:

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

nãofunciona...

Alémdisso,seeutiverumaimagem,eescalá-lausandocss("transform","scale(1.1) , ela parece se comportar onde o valor .getBoundingClientRect().top desce, não aumenta, o que torna o gráfico MDN mais confuso para mim, tanto quanto eu posso dizer eles mostram o oposto acontecendo. Talvez seja um problema 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;
}
    
por Summer Developer 12.01.2018 в 17:50
fonte

1 resposta

2

O transform é aplicado à posição original, portanto, fazer cálculos que levam em conta o transform para ajustar o transform é necessariamente complicado. Você deve levar em consideração a ordem das transformações, a posição original e a origem da transformação.

Por exemplo, aqui, um quadrado traduzido para 100px à esquerda e dimensionado para 0,5 estará na posição 125px. Primeiro, ele é movido para 100px e, em seguida, dimensionado do centro para 125px. Se você dimensionar antes, então você está em 75px. Escalado para 50 px a partir do centro (então a 25px à esquerda), em seguida, traduzido em 100px escalado para 50px. Então, obter as coordenadas certas pode ser complicado.

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>

Uma abordagem mais simples no seu caso, eu acho, seria trabalhar com transform-origin . Sua função updateCenter é exatamente o que transform-origin faz, define o centro. E está embutido. Todos os cálculos serão tratados pela transformação. Você só precisa definir a origem de que precisa e é bastante simples.

Digamos que você queira que o centro seja o ponto que está no centro do círculo em seu exemplo. Você precisa encontrar com percentual esse ponto está na imagem que você está transformando. Então é basicamente o

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

Que no seu caso dá:

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

matrix.41 é a tradução à esquerda da imagem que você pode encontrar dessa maneira (veja esta resposta: Como obter o valor do translateX por javascript ):

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

então você atribui:

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

Obviamente, você precisa fazer isso apenas ao mover a imagem, não no zoom, pois a origem já será tratada. Você também pode ajustar apenas em certas condições, mas em qualquer caso, você não precisa pensar sobre a tradução e o zoom.

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;
}
    
por Julien Grégoire 16.01.2018 / 19:57
fonte