如何计算元素旋转的变换translate(x,y)补偿?

时间:2019-01-21 11:32:28

标签: javascript css css3 css-transforms

我正在制作个人资料图片裁剪编辑器,该编辑器可以在区域内拖动,缩放和旋转图像。

图像的拖动是通过捕获区域的mousedown和mousemove事件并计算光标在区域内的x / y坐标的开始和结束以获取光标移动的距离来完成的。然后将此值添加到图像的当前内联样式变换的translate(x,y)值中(或从中减去)(取决于方向)。

var dragArea = document.getElementById('drag-area');
var photoImg = document.getElementById('photo');
var cropCircle = document.getElementById('crop-circle');
var cloneContainer = document.getElementById('clone-container');
var resetAll = document.getElementById('reset-all');
var scaleSlider = document.getElementById('scale-slider');
var scaleInput = document.getElementById('scale-input');
var scaleReset = document.getElementById('scale-reset');
var rotateSlider = document.getElementById('rotate-slider');
var rotateInput = document.getElementById('rotate-input');
var rotateReset = document.getElementById('rotate-reset');
var area = {}, photo = {
  translate: {
    x: 0, y: 0
  }, 
  transformOrigin: {
    x: 0, y: 0
  }
};

photoImg.src = photoSrc();
photoImg.style.top = cropCircle.offsetTop+'px';
photoImg.style.left = cropCircle.offsetLeft+'px';
photoImg.style.transform = 'scale(1) rotate(0deg) translate(0px, 0px)';
photoImg.style.transformOrigin = '0px 0px';
photoImg.onload = function() {
  if (this.naturalWidth < this.naturalHeight) {
    this.width = cropCircle.clientWidth;
  } else if (this.naturalWidth > this.naturalHeight) {
    this.height = cropCircle.clientHeight;
  } else {
    this.height = cropCircle.clientHeight;
    this.width = cropCircle.clientWidth;
  }
}

dragArea.onmouseenter = function() {
  this.onmousedown = function(e) {
    var transform = photoImg.style.transform;
    var photoStyle = window.getComputedStyle(photoImg);
    var photoMatrix = new DOMMatrix(photoStyle.transform);
    var transformOrigin = photoImg.style.transformOrigin.replace(/px/g, '').split(' ');
  
    photo = {
      translate: {},
      x: photoMatrix.m41,
      y: photoMatrix.m42,
      scale: Number(/scale\((-?\d+(?:\.\d*)?)\)/.exec(transform)[1]),
      rotate: Number(/rotate\((-?\d+(?:\.\d*)?)deg\)/.exec(transform)[1]),
      transformOrigin: {
        x: Number(transformOrigin[0]),
        y: Number(transformOrigin[1])
      }
    }

    area = {
      start: {
        x: e.offsetX + (e.target == cropCircle ? cropCircle.offsetLeft : 0),
        y: e.offsetY + (e.target == cropCircle ? cropCircle.offsetTop : 0)
      },
      distance: {
        x: 0,
        y: 0
      }
    };

    this.onmousemove = function(e) {
      area.end = {
        x: e.offsetX + (e.target == cropCircle ? cropCircle.offsetLeft : 0),
        y: e.offsetY + (e.target == cropCircle ? cropCircle.offsetTop : 0)
      };

      if (area.end.x > area.start.x) {
        area.distance.x = {
          type: 'positive', // right
          total: area.end.x - area.start.x
        }
      } else {
        area.distance.x = {
          type: 'negative', // left
          total: area.start.x - area.end.x
        }
      }
      if (area.end.y > area.start.y) {
        area.distance.y = {
          type: 'positive', // down
          total: area.end.y - area.start.y
        }
      } else {
        area.distance.y = {
          type: 'negative', // up
          total: area.start.y - area.end.y
        }
      }

      if (area.distance.x.type == 'positive') {
        photo.translate.x = photo.x + area.distance.x.total;
      } else {
        photo.translate.x = photo.x - area.distance.x.total;
      }
      if (area.distance.y.type == 'positive') {
        photo.translate.y = photo.y + area.distance.y.total;
      } else {
        photo.translate.y = photo.y - area.distance.y.total;
      }

      photoTransform({x: photo.translate.x, y: photo.translate.y});
    }
  }
}

dragArea.onmouseleave = function() {
  this.onmousemove = function(e) {
    e.preventDefault();
  }
}

dragArea.onmouseup = function() {
  this.onmousemove = function(e) {
    e.preventDefault();
  }
}

resetAll.onclick = function() {
  scaleSlider.value = scaleReset.value;
  scaleInput.value = scaleReset.value;
  rotateSlider.value = rotateReset.value;
  rotateInput.value = rotateReset.value;
  photo = {
    translate: {
      x: 0, y: 0
    }, 
    transformOrigin: {
      x: 0, y: 0
    }
  };
  photoTransform({scale: 1, rotate: '0', x: '0', y: '0'});
}

scaleSlider.oninput = function() {
  var value = this.value;
  scaleInput.value = value;
  photoTransform({scale: value});
}
scaleInput.oninput = function() {
  var value = this.value;
  this.value = value.length ? value : scaleReset.value;
  scaleSlider.value = this.value;
  photoTransform({scale: this.value});
}
scaleInput.onkeydown = function(e) {
  if (e.keyCode == 13) this.blur();
}
scaleInput.onblur = function() {
  var value = this.value;
  this.value = value.length ? value : scaleReset.value;
  scaleSlider.value = this.value;
  photoTransform({scale: this.value});
}
scaleReset.onclick = function() {
  scaleSlider.value = this.value;
  scaleInput.value = this.value;
  photoTransform({scale: this.value});
}

rotateSlider.oninput = function() {
  var value = this.value;
  rotateInput.value = value;
  photoTransform({rotate: value});
}
rotateInput.oninput = function() {
  var value = this.value;
  this.value = value.length ? value : rotateReset.value;
  rotateSlider.value = this.value;
  photoTransform({rotate: this.value});
}
rotateInput.onkeydown = function(e) {
  if (e.keyCode == 13) this.blur();
}
rotateInput.onblur = function() {
  var value = this.value;
  this.value = value.length ? value : rotateReset.value;
  rotateSlider.value = this.value;
  photoTransform({rotate: this.value});
}
rotateReset.onclick = function() {
  rotateSlider.value = this.value;
  rotateInput.value = this.value;
  photoTransform({rotate: this.value});
}

function photoTransform(property) {
  property = property || {};
  var transform = photoImg.style.transform;
  var axisX = property.axisX || photo.transformOrigin.x || (cropCircle.getBoundingClientRect().width / 2);
  var axisY = property.axisY || photo.transformOrigin.y || (cropCircle.getBoundingClientRect().height / 2);
  var scale = property.scale || photo.scale || Number(/scale\((-?\d+(?:\.\d*)?)\)/.exec(transform)[1]);
  var rotate = property.rotate || photo.rotate || Number(/rotate\((-?\d+(?:\.\d*)?)deg\)/.exec(transform)[1]);
  var translate = /translate\((-?\d+(?:\.\d*)?)px, (-?\d+(?:\.\d*)?)px\)/.exec(transform);
  var translateX = (property.x || photo.translate.x || Number(translate[1])) / scale;
  var translateY = (property.y || photo.translate.y || Number(translate[2])) / scale;

  photoImg.style.transformOrigin = axisX+'px '+axisY+'px';
  photoImg.style.transform = 'scale('+scale+') rotate('+rotate+'deg) translate('+translateX+'px, '+translateY+'px)';
  
  photo.transformOrigin = {
    x: axisX,
    y: axisY
  }
  photo.scale = scale;
  photo.rotate = rotate;
}

function photoSrc() {
  return 'data:image/jpeg;base64,/9j/4QVuRXhpZgAATU0AKgAAAAgADAEAAAMAAAABAMgAAAEBAAMAAAABAMgAAAECAAMAAAADAAAAngEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEVAAMAAAABAAMAAAEaAAUAAAABAAAApAEbAAUAAAABAAAArAEoAAMAAAABAAIAAAExAAIAAAAkAAAAtAEyAAIAAAAUAAAA2IdpAAQAAAABAAAA7AAAASQACAAIAAgACvyAAAAnEAAK/IAAACcQQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkAMjAxOTowMToxOCAxNzo1ODoxNgAABJAAAAcAAAAEMDIyMaABAAMAAAAB//8AAKACAAQAAAABAAAAyKADAAQAAAABAAAAyAAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAFyARsABQAAAAEAAAF6ASgAAwAAAAEAAgAAAgEABAAAAAEAAAGCAgIABAAAAAEAAAPkAAAAAAAAAEgAAAABAAAASAAAAAH/2P/tAAxBZG9iZV9DTQAC/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAoACgAwEiAAIRAQMRAf/dAAQACv/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8AAkkkuweLUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKf/9ACSSS7B4tSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkp//0QJJJLsHi1JJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSn//SAkkkuweLUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKf/9MCSSS7B4tSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkp//1AJJJLsHi1JJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSn//VAkkkuweLUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKf/9YCSSS7B4tSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkp//1wJJJLsHi1JJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSn//QAkkkuweLUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKf/9n/7Q1MUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAA8cAVoAAxslRxwCAAACAAAAOEJJTQQlAAAAAAAQzc/6fajHvgkFcHaurwXDTjhCSU0EOgAAAAAA5QAAABAAAAABAAAAAAALcHJpbnRPdXRwdXQAAAAFAAAAAFBzdFNib29sAQAAAABJbnRlZW51bQAAAABJbnRlAAAAAENscm0AAAAPcHJpbnRTaXh0ZWVuQml0Ym9vbAAAAAALcHJpbnRlck5hbWVURVhUAAAAAQAAAAAAD3ByaW50UHJvb2ZTZXR1cE9iamMAAAAMAFAAcgBvAG8AZgAgAFMAZQB0AHUAcAAAAAAACnByb29mU2V0dXAAAAABAAAAAEJsdG5lbnVtAAAADGJ1aWx0aW5Qcm9vZgAAAAlwcm9vZkNNWUsAOEJJTQQ7AAAAAAItAAAAEAAAAAEAAAAAABJwcmludE91dHB1dE9wdGlvbnMAAAAXAAAAAENwdG5ib29sAAAAAABDbGJyYm9vbAAAAAAAUmdzTWJvb2wAAAAAAENybkNib29sAAAAAABDbnRDYm9vbAAAAAAATGJsc2Jvb2wAAAAAAE5ndHZib29sAAAAAABFbWxEYm9vbAAAAAAASW50cmJvb2wAAAAAAEJja2dPYmpjAAAAAQAAAAAAAFJHQkMAAAADAAAAAFJkICBkb3ViQG/gAAAAAAAAAAAAR3JuIGRvdWJAb+AAAAAAAAAAAABCbCAgZG91YkBv4AAAAAAAAAAAAEJyZFRVbnRGI1JsdAAAAAAAAAAAAAAAAEJsZCBVbnRGI1JsdAAAAAAAAAAAAAAAAFJzbHRVbnRGI1B4bEBSAAAAAAAAAAAACnZlY3RvckRhdGFib29sAQAAAABQZ1BzZW51bQAAAABQZ1BzAAAAAFBnUEMAAAAATGVmdFVudEYjUmx0AAAAAAAAAAAAAAAAVG9wIFVudEYjUmx0AAAAAAAAAAAAAAAAU2NsIFVudEYjUHJjQFkAAAAAAAAAAAAQY3JvcFdoZW5QcmludGluZ2Jvb2wAAAAADmNyb3BSZWN0Qm90dG9tbG9uZwAAAAAAAAAMY3JvcFJlY3RMZWZ0bG9uZwAAAAAAAAANY3JvcFJlY3RSaWdodGxvbmcAAAAAAAAAC2Nyb3BSZWN0VG9wbG9uZwAAAAAAOEJJTQPtAAAAAAAQAEgAAAABAAIASAAAAAEAAjhCSU0EJgAAAAAADgAAAAAAAAAAAAA/gAAAOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAjhCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAAAAAAAAAIAADhCSU0EAgAAAAAAAgAAOEJJTQQwAAAAAAABAQA4QklNBC0AAAAAAAIAADhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAA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NzIyLWNhNDkzZjBjMzE5MyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MjUyN2FhMS1jMjhmLTQ3ZjEtYWRiMi00MWZlYThhY2JkMGQiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0iRUFBQTAyMEJDQ0NGNDZGNzdCQ0NCNUJEQjVFRkRFREEiIGRjOmZvcm1hdD0iaW1hZ2UvanBlZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9IiIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDEtMThUMTc6NTY6MzMrMDE6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjQ1YjRhOGYtMmYzNC00NzVjLWEzYTMtMGJjMmI2ZjAwYWEzIiBzdEV2dDp3aGVuPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjI1MjdhYTEtYzI4Zi00N2YxLWFkYjItNDFmZWE4YWNiZDBkIiBzdEV2dDp3aGVuPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/uAA5BZG9iZQBkgAAAAAH/2wCEACAhITMkM1EwMFFCLy8vQiccHBwcJyIXFxcXFyIRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBIjMzNCY0IhgYIhQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAMgAyAMBIgACEQEDEQH/3QAEAA3/xAEbAAADAQEBAQEBAQEBAAAAAAABAAIDBAUGBwgJCgsBAQEBAQEBAQEBAQEBAAAAAAABAgMEBQYHCAkKCxAAAgIBAwIDBAcGAwMGAgE1AQACEQMhEjEEQVEiE2FxMoGRsUKhBdHBFPBSI3IzYuGC8UM0kqKyFdJTJHPCYwaDk+Lyo0RUZCU1RRYmdDZVZbOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYRAAICAAUBBgYBAwEDBQMGLwABEQIhAzFBElFhcYGRIhMy8KGxBMHR4fFCUiNichSSM4JDJKKyNFNEY3PC0oOTo1Ti8gUVJQYWJjVkRVU2dGWzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdob/2gAMAwEAAhEDEQA/AIVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//QhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9GFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//0oVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//ThVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9SFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//1YVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//WhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9eFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//0IVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//RhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9KFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//04V8JX6R8s91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA//2Q==';
}
body {
  background-color: #eff1f3;
}
#profile-picture {
  width: 370px;
  height: 330px;
  margin: auto;
}
#profile-picture * {
  user-select: none;
}
#drag-area {
  width: 100%;
  height: 100%;
  cursor: move;
  cursor: grab;
  display: block;
  overflow: hidden;
  position: relative;
  background-color: #000;
  background-repeat: repeat;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJElEQVQoU2M8e/asMQMaMDY2PosuxjgUFKI7GsTH5m7GIaAQAA4fIQ1WOLcWAAAAAElFTkSuQmCC');
}
#drag-area:active {
  cursor: grabbing;
}
#clone-container {
  width: 0px;
  height: 0px;
  display: block;
  overflow: hidden;
  position: absolute;
}
#photo, #photo-clone {
  display: block;
  min-width: 230px;
  min-height: 230px;
  position: absolute;
  pointer-events: none;
}
img[src=''] {
  visibility: hidden;
}
#crop-circle {
  width: 230px;
  height: 230px;
  margin: 50px auto;
  overflow: hidden;
  position: relative;
  border-radius: 50%;
  box-shadow: 0 0 0 2px #fff, 0 0 0 100vw rgba(0,0,0,0.5);
}
#circle-thirds {
  top: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: absolute;
  pointer-events: none;
  border-radius: 100%;
}
#circle-thirds * {
  z-index: 1;
  position: absolute;
  background-color: rgba(226,226,226,0.5);
}
#circle-thirds .top-horizontal {
  width: 100%;
  height: 1px;
  top: 33.33333%;
}
#circle-thirds .bottom-horizontal {
  width: 100%;
  height: 1px;
  top: 66.66666%;
}
#circle-thirds .left-vertical {
  height: 100%;
  width: 1px;
  left: 33.33333%;
}
#circle-thirds .right-vertical {
  height: 100%;
  width: 1px;
  left: 66.66666%;
}
.photo-options {
  width: 100%;
  display: block;
  position: relative;
  padding-top: 15px;
}
.option-buttons {
  width: 100%;
  display: flex;
  position: relative;
  padding-bottom: 10px;
  justify-content: space-between;
}
.option-buttons button {
  width: 100%;
}
.option-buttons button + button {
  margin-left: 10px;
}
.photo-options fieldset {
  margin: 0px;
}
.photo-options fieldset + fieldset {
  margin-top: 10px;
}
.option-slider {
  display: flex;
  position: relative;
}
.option-slider input[type=range] {
  width: 50%;
  flex-shrink: 0;
}
.option-slider input[type=number] {
  width: 20%;
  margin: 0 10px;
}
.option-slider button {
  width: 30%;
}
<div id="profile-picture">
  <div id="drag-area">
    <div id="clone-container"></div>
    <img id="photo" src="">
    <div id="crop-circle">
      <div id="circle-thirds">
        <span class="top-horizontal"></span>
        <span class="bottom-horizontal"></span>
        <span class="left-vertical"></span>
        <span class="right-vertical"></span>
      </div>
    </div>
  </div>
  <div class="photo-options">
    <div class="option-buttons">
      <button id="reset-all">Reset everything</button>
    </div>
    <fieldset>
      <legend>Scale</legend>
        <div class="option-slider">
        <input type="range" id="scale-slider" min="1" max="3" step="0.01" value="1">
        <input type="number" id="scale-input" min="1" max="3" step="0.01" value="1">
        <button id="scale-reset" value="1">Reset</button>
      </div>
    </fieldset>
    <fieldset>
      <legend>Rotate</legend>
      <div class="option-slider">
        <input type="range" id="rotate-slider" min="-180" max="180" step="1" value="0">
        <input type="number" id="rotate-input" min="-180" max="180" step="1" value="0">
        <button id="rotate-reset" value="0">Reset</button>
      </div>
    </fieldset>
  </div>
</div>

问题在于,旋转图像时,其translate(x y)值不再相对于该区域的x / y坐标。

我找到了几个示例,这些示例如何使用旋转角度,cos和sin的弧度来计算旋转的正方形或矩形的四个角的x / y坐标。但是由于几何不是我的强项,所以我不知道这些计算将如何应用于图像的translate(x,y)值。

这是图像裁剪器当前工作方式的笔:https://codepen.io/ClubAce/pen/maNJNZ

无论旋转方向如何,图像都应沿区域的x / y轴拖动。

我真的希望有人能帮助我弄清楚如何修改脚本以产生所需的拖动行为。

谢谢。

2 个答案:

答案 0 :(得分:3)

您只需要更改以下顺序:

 photoImg.style.transform = 'scale('+scale+') rotate('+rotate+'deg)  translate('+translateX+'px, '+translateY+'px) ';

对此

photoImg.style.transform = 'scale('+scale+') translate('+translateX+'px, '+translateY+'px) rotate('+rotate+'deg) ';

Order is very important when using Transform

完整代码:

var dragArea = document.getElementById('drag-area');
var photoImg = document.getElementById('photo');
var cropCircle = document.getElementById('crop-circle');
var cloneContainer = document.getElementById('clone-container');
var resetAll = document.getElementById('reset-all');
var scaleSlider = document.getElementById('scale-slider');
var scaleInput = document.getElementById('scale-input');
var scaleReset = document.getElementById('scale-reset');
var rotateSlider = document.getElementById('rotate-slider');
var rotateInput = document.getElementById('rotate-input');
var rotateReset = document.getElementById('rotate-reset');
var area = {}, photo = {
  translate: {
    x: 0, y: 0
  }, 
  transformOrigin: {
    x: 0, y: 0
  }
};

photoImg.src = photoSrc();
photoImg.style.top = cropCircle.offsetTop+'px';
photoImg.style.left = cropCircle.offsetLeft+'px';
photoImg.style.transform = 'scale(1) rotate(0deg) translate(0px, 0px)';
photoImg.style.transformOrigin = '0px 0px';
photoImg.onload = function() {
  if (this.naturalWidth < this.naturalHeight) {
    this.width = cropCircle.clientWidth;
  } else if (this.naturalWidth > this.naturalHeight) {
    this.height = cropCircle.clientHeight;
  } else {
    this.height = cropCircle.clientHeight;
    this.width = cropCircle.clientWidth;
  }
}

dragArea.onmouseenter = function() {
  this.onmousedown = function(e) {
    var transform = photoImg.style.transform;
    var photoStyle = window.getComputedStyle(photoImg);
    var photoMatrix = new DOMMatrix(photoStyle.transform);
    var transformOrigin = photoImg.style.transformOrigin.replace(/px/g, '').split(' ');
  
    photo = {
      translate: {},
      x: photoMatrix.m41,
      y: photoMatrix.m42,
      scale: Number(/scale\((-?\d+(?:\.\d*)?)\)/.exec(transform)[1]),
      rotate: Number(/rotate\((-?\d+(?:\.\d*)?)deg\)/.exec(transform)[1]),
      transformOrigin: {
        x: Number(transformOrigin[0]),
        y: Number(transformOrigin[1])
      }
    }

    area = {
      start: {
        x: e.offsetX + (e.target == cropCircle ? cropCircle.offsetLeft : 0),
        y: e.offsetY + (e.target == cropCircle ? cropCircle.offsetTop : 0)
      },
      distance: {
        x: 0,
        y: 0
      }
    };

    this.onmousemove = function(e) {
      area.end = {
        x: e.offsetX + (e.target == cropCircle ? cropCircle.offsetLeft : 0),
        y: e.offsetY + (e.target == cropCircle ? cropCircle.offsetTop : 0)
      };

      if (area.end.x > area.start.x) {
        area.distance.x = {
          type: 'positive', // right
          total: area.end.x - area.start.x
        }
      } else {
        area.distance.x = {
          type: 'negative', // left
          total: area.start.x - area.end.x
        }
      }
      if (area.end.y > area.start.y) {
        area.distance.y = {
          type: 'positive', // down
          total: area.end.y - area.start.y
        }
      } else {
        area.distance.y = {
          type: 'negative', // up
          total: area.start.y - area.end.y
        }
      }

      if (area.distance.x.type == 'positive') {
        photo.translate.x = photo.x + area.distance.x.total;
      } else {
        photo.translate.x = photo.x - area.distance.x.total;
      }
      if (area.distance.y.type == 'positive') {
        photo.translate.y = photo.y + area.distance.y.total;
      } else {
        photo.translate.y = photo.y - area.distance.y.total;
      }

      photoTransform({x: photo.translate.x, y: photo.translate.y});
    }
  }
}

dragArea.onmouseleave = function() {
  this.onmousemove = function(e) {
    e.preventDefault();
  }
}

dragArea.onmouseup = function() {
  this.onmousemove = function(e) {
    e.preventDefault();
  }
}

resetAll.onclick = function() {
  scaleSlider.value = scaleReset.value;
  scaleInput.value = scaleReset.value;
  rotateSlider.value = rotateReset.value;
  rotateInput.value = rotateReset.value;
  photo = {
    translate: {
      x: 0, y: 0
    }, 
    transformOrigin: {
      x: 0, y: 0
    }
  };
  photoTransform({scale: 1, rotate: '0', x: '0', y: '0'});
}

scaleSlider.oninput = function() {
  var value = this.value;
  scaleInput.value = value;
  photoTransform({scale: value});
}
scaleInput.oninput = function() {
  var value = this.value;
  this.value = value.length ? value : scaleReset.value;
  scaleSlider.value = this.value;
  photoTransform({scale: this.value});
}
scaleInput.onkeydown = function(e) {
  if (e.keyCode == 13) this.blur();
}
scaleInput.onblur = function() {
  var value = this.value;
  this.value = value.length ? value : scaleReset.value;
  scaleSlider.value = this.value;
  photoTransform({scale: this.value});
}
scaleReset.onclick = function() {
  scaleSlider.value = this.value;
  scaleInput.value = this.value;
  photoTransform({scale: this.value});
}

rotateSlider.oninput = function() {
  var value = this.value;
  rotateInput.value = value;
  photoTransform({rotate: value});
}
rotateInput.oninput = function() {
  var value = this.value;
  this.value = value.length ? value : rotateReset.value;
  rotateSlider.value = this.value;
  photoTransform({rotate: this.value});
}
rotateInput.onkeydown = function(e) {
  if (e.keyCode == 13) this.blur();
}
rotateInput.onblur = function() {
  var value = this.value;
  this.value = value.length ? value : rotateReset.value;
  rotateSlider.value = this.value;
  photoTransform({rotate: this.value});
}
rotateReset.onclick = function() {
  rotateSlider.value = this.value;
  rotateInput.value = this.value;
  photoTransform({rotate: this.value});
}

function photoTransform(property) {
  property = property || {};
  var transform = photoImg.style.transform;
  var axisX = property.axisX || photo.transformOrigin.x || (cropCircle.getBoundingClientRect().width / 2);
  var axisY = property.axisY || photo.transformOrigin.y || (cropCircle.getBoundingClientRect().height / 2);
  var scale = property.scale || photo.scale || Number(/scale\((-?\d+(?:\.\d*)?)\)/.exec(transform)[1]);
  var rotate = property.rotate || photo.rotate || Number(/rotate\((-?\d+(?:\.\d*)?)deg\)/.exec(transform)[1]);
  var translate = /translate\((-?\d+(?:\.\d*)?)px, (-?\d+(?:\.\d*)?)px\)/.exec(transform);
  var translateX = (property.x || photo.translate.x || Number(translate[1])) / scale;
  var translateY = (property.y || photo.translate.y || Number(translate[2])) / scale;

  photoImg.style.transformOrigin = axisX+'px '+axisY+'px';
  photoImg.style.transform = 'scale('+scale+') translate('+translateX+'px, '+translateY+'px) rotate('+rotate+'deg) ';
  
  photo.transformOrigin = {
    x: axisX,
    y: axisY
  }
  photo.scale = scale;
  photo.rotate = rotate;
}

function photoSrc() {
  return 'data:image/jpeg;base64,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AENybkNib29sAAAAAABDbnRDYm9vbAAAAAAATGJsc2Jvb2wAAAAAAE5ndHZib29sAAAAAABFbWxEYm9vbAAAAAAASW50cmJvb2wAAAAAAEJja2dPYmpjAAAAAQAAAAAAAFJHQkMAAAADAAAAAFJkICBkb3ViQG/gAAAAAAAAAAAAR3JuIGRvdWJAb+AAAAAAAAAAAABCbCAgZG91YkBv4AAAAAAAAAAAAEJyZFRVbnRGI1JsdAAAAAAAAAAAAAAAAEJsZCBVbnRGI1JsdAAAAAAAAAAAAAAAAFJzbHRVbnRGI1B4bEBSAAAAAAAAAAAACnZlY3RvckRhdGFib29sAQAAAABQZ1BzZW51bQAAAABQZ1BzAAAAAFBnUEMAAAAATGVmdFVudEYjUmx0AAAAAAAAAAAAAAAAVG9wIFVudEYjUmx0AAAAAAAAAAAAAAAAU2NsIFVudEYjUHJjQFkAAAAAAAAAAAAQY3JvcFdoZW5QcmludGluZ2Jvb2wAAAAADmNyb3BSZWN0Qm90dG9tbG9uZwAAAAAAAAAMY3JvcFJlY3RMZWZ0bG9uZwAAAAAAAAANY3JvcFJlY3RSaWdodGxvbmcAAAAAAAAAC2Nyb3BSZWN0VG9wbG9uZwAAAAAAOEJJTQPtAAAAAAAQAEgAAAABAAIASAAAAAEAAjhCSU0EJgAAAAAADgAAAAAAAAAAAAA/gAAAOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAjhCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAAAAAAAAAIAADhCSU0EAgAAAAAAAgAAOEJJTQQwAAAAAAABAQA4QklNBC0AAAAAAAIAADhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAA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NzIyLWNhNDkzZjBjMzE5MyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MjUyN2FhMS1jMjhmLTQ3ZjEtYWRiMi00MWZlYThhY2JkMGQiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0iRUFBQTAyMEJDQ0NGNDZGNzdCQ0NCNUJEQjVFRkRFREEiIGRjOmZvcm1hdD0iaW1hZ2UvanBlZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9IiIgeG1wOkNyZWF0ZURhdGU9IjIwMTktMDEtMThUMTc6NTY6MzMrMDE6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjQ1YjRhOGYtMmYzNC00NzVjLWEzYTMtMGJjMmI2ZjAwYWEzIiBzdEV2dDp3aGVuPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NjI1MjdhYTEtYzI4Zi00N2YxLWFkYjItNDFmZWE4YWNiZDBkIiBzdEV2dDp3aGVuPSIyMDE5LTAxLTE4VDE3OjU4OjE2KzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/uAA5BZG9iZQBkgAAAAAH/2wCEACAhITMkM1EwMFFCLy8vQiccHBwcJyIXFxcXFyIRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBIjMzNCY0IhgYIhQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAMgAyAMBIgACEQEDEQH/3QAEAA3/xAEbAAADAQEBAQEBAQEBAAAAAAABAAIDBAUGBwgJCgsBAQEBAQEBAQEBAQEBAAAAAAABAgMEBQYHCAkKCxAAAgIBAwIDBAcGAwMGAgE1AQACEQMhEjEEQVEiE2FxMoGRsUKhBdHBFPBSI3IzYuGC8UM0kqKyFdJTJHPCYwaDk+Lyo0RUZCU1RRYmdDZVZbOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYRAAICAAUBBgYBAwEDBQMGLwABEQIhAzFBElFhcYGRIhMy8KGxBMHR4fFCUiNichSSM4JDJKKyNFNEY3PC0oOTo1Ti8gUVJQYWJjVkRVU2dGWzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdob/2gAMAwEAAhEDEQA/AIVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//QhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9GFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//0oVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//ThVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9SFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//1YVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//WhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9eFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//0IVVfpHyxVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUD//RhVV+kfLFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQP/9KFVX6R8sVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVAVVUBVVQFVVA//04V8JX6R8s91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA91XwlQPdV8JUD3VfCVA//2Q==';
}
body {
  background-color: #eff1f3;
}
#profile-picture {
  width: 370px;
  height: 330px;
  margin: auto;
}
#profile-picture * {
  user-select: none;
}
#drag-area {
  width: 100%;
  height: 100%;
  cursor: move;
  cursor: grab;
  display: block;
  overflow: hidden;
  position: relative;
  background-color: #000;
  background-repeat: repeat;
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJElEQVQoU2M8e/asMQMaMDY2PosuxjgUFKI7GsTH5m7GIaAQAA4fIQ1WOLcWAAAAAElFTkSuQmCC');
}
#drag-area:active {
  cursor: grabbing;
}
#clone-container {
  width: 0px;
  height: 0px;
  display: block;
  overflow: hidden;
  position: absolute;
}
#photo, #photo-clone {
  display: block;
  min-width: 230px;
  min-height: 230px;
  position: absolute;
  pointer-events: none;
}
img[src=''] {
  visibility: hidden;
}
#crop-circle {
  width: 230px;
  height: 230px;
  margin: 50px auto;
  overflow: hidden;
  position: relative;
  border-radius: 50%;
  box-shadow: 0 0 0 2px #fff, 0 0 0 100vw rgba(0,0,0,0.5);
}
#circle-thirds {
  top: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: absolute;
  pointer-events: none;
  border-radius: 100%;
}
#circle-thirds * {
  z-index: 1;
  position: absolute;
  background-color: rgba(226,226,226,0.5);
}
#circle-thirds .top-horizontal {
  width: 100%;
  height: 1px;
  top: 33.33333%;
}
#circle-thirds .bottom-horizontal {
  width: 100%;
  height: 1px;
  top: 66.66666%;
}
#circle-thirds .left-vertical {
  height: 100%;
  width: 1px;
  left: 33.33333%;
}
#circle-thirds .right-vertical {
  height: 100%;
  width: 1px;
  left: 66.66666%;
}
.photo-options {
  width: 100%;
  display: block;
  position: relative;
  padding-top: 15px;
}
.option-buttons {
  width: 100%;
  display: flex;
  position: relative;
  padding-bottom: 10px;
  justify-content: space-between;
}
.option-buttons button {
  width: 100%;
}
.option-buttons button + button {
  margin-left: 10px;
}
.photo-options fieldset {
  margin: 0px;
}
.photo-options fieldset + fieldset {
  margin-top: 10px;
}
.option-slider {
  display: flex;
  position: relative;
}
.option-slider input[type=range] {
  width: 50%;
  flex-shrink: 0;
}
.option-slider input[type=number] {
  width: 20%;
  margin: 0 10px;
}
.option-slider button {
  width: 30%;
}
<div id="profile-picture">
  <div id="drag-area">
    <div id="clone-container"></div>
    <img id="photo" src="">
    <div id="crop-circle">
      <div id="circle-thirds">
        <span class="top-horizontal"></span>
        <span class="bottom-horizontal"></span>
        <span class="left-vertical"></span>
        <span class="right-vertical"></span>
      </div>
    </div>
  </div>
  <div class="photo-options">
    <div class="option-buttons">
      <button id="reset-all">Reset everything</button>
    </div>
    <fieldset>
      <legend>Scale</legend>
        <div class="option-slider">
        <input type="range" id="scale-slider" min="1" max="3" step="0.01" value="1">
        <input type="number" id="scale-input" min="1" max="3" step="0.01" value="1">
        <button id="scale-reset" value="1">Reset</button>
      </div>
    </fieldset>
    <fieldset>
      <legend>Rotate</legend>
      <div class="option-slider">
        <input type="range" id="rotate-slider" min="-180" max="180" step="1" value="0">
        <input type="number" id="rotate-input" min="-180" max="180" step="1" value="0">
        <button id="rotate-reset" value="0">Reset</button>
      </div>
    </fieldset>
  </div>
</div>

答案 1 :(得分:1)

您必须将旋转应用于平移,这听起来很复杂,而且如果您不熟悉几何,也许是这样。但基本上,我们将平移视为同一坐标系中的单个点,然后对其应用旋转。

function transformTranslateX(translateX, translateY, rotationDegrees) {
    rotationRadians = rotationDegrees * Math.PI / 180;
    return translateX * Math.cos(rotationRadians) - translateY * Math.sin(rotationRadians);
}

function transformTranslateY(translateX, translateY, rotationDegrees) {
    rotationRadians = rotationDegrees * Math.PI / 180;
    return translateX * Math.sin(rotationRadians) + translateY * Math.cos(rotationRadians);
}

我尚未测试此代码,因此可能存在错误。