Simulating Transform-Origin Using Translate

Simulating transform-origin using translate

You are almost good but you have two errors. You need to invert the translations and you need to change the transform-origin of the second one.

If you check the documentation, you will see that the reference used to translate the origin is the top left corner of the element and the default value of transform origin is center. So we need to have the same reference for both.

.origin {  transform-origin: 50px 50px;  transform:  rotate(45deg) scale(2);}.translate {  transform-origin:0 0;   transform:translate(50px, 50px) rotate(45deg) scale(2) translate(-50px, -50px);} .box {  background-color: red;  width: 50px;  height: 50px;}.container {  display: inline-block;  margin: 30px;  width: 150px;  height: 150px;  background-color: rgba(0,0,0,0.1);}
<div class="container">  <div class="box origin">  </div></div><div class="container">  <div class="box translate">  </div></div>

Understanding translate after scale in CSS transforms

Since the translation is done after the scale() it will also get scaled so your 320px need to be divided by 0.9 to get the correct value:

320/0.9 = 355.56

In other words, you need to move by 355.56px to actually get the 320px. It's a bit tricky but imagine your self inside another world scaled by 0.9. The perception of the distances outside that world will not be the same inside the scaled world.

A related question to get more details about the math: Why does order of transforms matter? rotate/scale doesn't give the same result as scale/rotate

In your case:

scale(0.9) translate(A, B)

Is equivalent to:

|0.9 0 0|   |1 0 A|   |0.9 0  A*0.9|
|0 0.9 0| x |0 1 B| = |0 0.9 B*0.9|
|0 0 1| |0 0 1| |0 0 1 |

So

Xf =  0.9*(Xi + A);
Yf = 0.9*(Yi + B);

If you do the opposite (translate then scale) you can use 320px

* {
padding: 0;
margin: 0;
}

.container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 6400px;
height: 3600px;
background-color: red;
transform-origin: 50% 50%;
transform: translate(-320px, -180px) scale(0.9) ;
}
<div class="container">

</div>

when an html element is translated then rotated it moves diagonally

It happens because the transforms take place in the order you write them.

It also provides better transform management because you might want to either

  • translate 200px to bottom and then rotate (in place, so you'll know the element does not go sideways):

    transform: translateY(200px) rotate(-15deg);
  • rotate and then translate 200px in the direction of the rotation:

    transform: rotate(-15deg) translateY(200px);

You can also imagine rotate rotating the XoY axis.

The translation written ahead of rotate (actually any previous operations) will take place within the normal axis setup.

The translation written after the rotate will take place within the rotated axis setup.

How to calculate transform translate(x, y) compensation for element rotation?

You simply need to change the order from this:

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

to this

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

Order is very important when using Transform

Full code:

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>

CSS 3d transform doesn't work if perspective is set in the end of property

You should make the perspective first in both cases. If you add it at the end the translation will be first made without considering the perspective.

If we refer to the specification we can see how the transformation matrix is computed:

The transformation matrix is computed from the transform and
transform-origin properties as follows:

  1. Start with the identity matrix.

  2. Translate by the computed X and Y of transform-origin

  3. Multiply by each of the transform functions in transform property from
    left to right

  4. Translate by the negated computed X and Y values of transform-origin

As you can see in the step (3), it's from left to right (here is another question where you can get more information and see why order is important: Simulating transform-origin using translate)

It also useless to use the perspective property within the element you want to transform.

box:nth-child(1):hover {  transform: perspective(1000px) translate3d(0, 0, -100px);}
box:nth-child(2):hover { transform: perspective(1000px) translate3d(0, 0, 100px);}
box { padding: 20px; display: inline-flex; align-items: center; justify-content: center; font-family: monospace; transition: transform .4s; background: rgba(255, 0, 0, 0.3); margin: 20px; /*perspective: 1000px;*/ font-size: 12px; cursor: pointer;}
box:nth-child(2) { background: rgba(0, 0, 255, 0.3);}
<box>  transform: perspective(1000px) translate3d(0,0,100px);</box><box>  transform:  perspective(1000px) translate3d(0,0,100px);</box>

How does rotation combined with translation work inside CSS animation?

From the specification we can see how the broswer should deal with interpolation between transform values. In this case we use this:

If from- and to-transform have the same number of transform functions,
each transform function pair has either the same name, or is a
derivative of the same primitive: Interpolate each transform function
pair as described in Interpolation of transform functions. The
computed value is the resulting transform function list.

So the browser will change the first rotate from 360deg to -360deg and the same for the last rotate while translateX will kept the same. We will then have the following steps:

transform: rotate(360deg) translateX(1.125em) rotate(-360deg);
transform: rotate(350deg) translateX(1.125em) rotate(-350deg);
transform: rotate(340deg) translateX(1.125em) rotate(-340deg);
....
transform: rotate(0) translateX(1.125em) rotate(0);
....
....
transform: rotate(-360deg) translateX(1.125em) rotate(360deg);

Now we need to understand how rotate(-adeg) translateX(b) rotate(adeg) works. First you may notice that the rotation won't have any visual effect on the element since we deal with a circle, it will simply affect how the translation will work and more precisely it's the first rotation that is important (the one in the left).

.container {  width: 50px;  height: 50px;  margin: 50px;  border:2px solid;}
.box { width: 50px; height: 50px; background: red; border-radius: 50%; animation: move 2s linear infinite;}
.alt { animation: move-alt 2s linear infinite;}
@keyframes move { from { transform: rotate(360deg) translateX(1.125em) rotate(-360deg); } to { transform: rotate(-360deg) translateX(1.125em) rotate(360deg); }}
@keyframes move-alt { from { transform: rotate(360deg) translateX(1.125em); } to { transform: rotate(-360deg) translateX(1.125em); }}
<div class="container">  <div class="box">
</div></div><div class="container"> <div class="box alt">
</div></div>

Calculating transform-origin of two overlapping elements

After fiddling around, I figured out the formula is:

(
(box.left - image.left) /
(image.width - box.width)
) * 100


Related Topics



Leave a reply



Submit