Skip to content

Commit 06d7f49

Browse files
author
Sepand Parhami
committed
Add a tool to generate markup for doing a cropped animation.
1 parent 4b01968 commit 06d7f49

File tree

6 files changed

+843
-0
lines changed

6 files changed

+843
-0
lines changed

docs/tools/img-cropper/dragger.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
const srcImgContainer = document.querySelector('.src-img-container');
2+
const selectionContainer = document.querySelector('.selection-container');
3+
const rect = document.querySelector('.selection-rect');
4+
const targetImg = document.querySelector('.target');
5+
const draggers = [0, 0, 0, 0].map(() => {
6+
return createDragger(srcImgContainer);
7+
});
8+
9+
let dragger = draggers[2];
10+
let mousedown = false;
11+
let x0 = 0
12+
let y0 = 0;
13+
let x1 = x0 + 96;
14+
let y1 = y0 + 96;
15+
let lastX = 0;
16+
let lastY = 0;
17+
18+
/**
19+
* @param {number} min
20+
* @param {number} value
21+
* @param {number} max
22+
* @return {number}
23+
*/
24+
function bound(min, value, max) {
25+
return Math.max(min, Math.min(value, max));
26+
}
27+
28+
/**
29+
* Gets the Rect for the currently selected coordinates.
30+
* @return {{
31+
* top: number,
32+
* bottom: number,
33+
* left: number,
34+
* right: number,
35+
* width: number,
36+
* height: number,
37+
* }}
38+
*/
39+
function getSelectedRect() {
40+
return {
41+
top: Math.min(y0, y1),
42+
bottom: Math.max(y0, y1),
43+
left: Math.min(x0, x1),
44+
right: Math.max(x0, x1),
45+
width: Math.abs(x0 - x1),
46+
height: Math.abs(y0 - y1),
47+
};
48+
}
49+
50+
/**
51+
* Notifies interested parties that a new area was selected.
52+
*/
53+
function notifySelected() {
54+
window.dispatchEvent(new CustomEvent('area-selected', {
55+
detail: getSelectedRect(),
56+
}));
57+
}
58+
59+
/**
60+
* Handles a mousedown on a dragger, setting it as the current dragger.
61+
*/
62+
function mousedownDragger() {
63+
mousedown = true;
64+
65+
dragger = event.target;
66+
event.stopPropagation();
67+
}
68+
69+
/**
70+
* Creates a dragger and appends it to the container.
71+
* @param {!Element} container
72+
*/
73+
function createDragger(container) {
74+
const div = document.createElement('div');
75+
div.className = 'dragger';
76+
div.onmousedown = mousedownDragger;
77+
container.appendChild(div);
78+
return div;
79+
}
80+
81+
/**
82+
* Updates the UI (the draggers and the highlight rect) for the currently
83+
* selected coordinates.
84+
*/
85+
function updateSelected() {
86+
const {
87+
top,
88+
left,
89+
width,
90+
height,
91+
} = getSelectedRect();
92+
draggers[0].style.transform = `translate(${x0}px, ${y0}px)`;
93+
draggers[1].style.transform = `translate(${x1}px, ${y0}px)`;
94+
draggers[2].style.transform = `translate(${x1}px, ${y1}px)`;
95+
draggers[3].style.transform = `translate(${x0}px, ${y1}px)`;
96+
rect.style.left = `${left}px`;
97+
rect.style.top = `${top}px`;
98+
rect.style.width = `${width}px`;
99+
rect.style.height = `${height}px`;
100+
}
101+
102+
/**
103+
* Updates the current coordinates based on the last mouse location and whether
104+
* or not the shift key is pressed. When the shift key is pressed, a 1:1 aspect
105+
* ration is forced.
106+
* @param {boolean} shiftKey
107+
*/
108+
function updateCoordinates(shiftKey) {
109+
const draggerIndex = draggers.indexOf(dragger);
110+
const initialRect = targetImg.getBoundingClientRect();
111+
const startX = draggerIndex == 0 || draggerIndex == 3 ? x1 : x0;
112+
const startY = draggerIndex == 0 || draggerIndex == 1 ? y1 : y0;
113+
const x = lastX - initialRect.left;
114+
const y = lastY - initialRect.top;
115+
const targetRect = targetImg.getBoundingClientRect();
116+
const boundX = bound(0, x, targetRect.width);
117+
const boundY = bound(0, y, targetRect.height);
118+
119+
let xDelta = startX - boundX;
120+
let yDelta = startY - boundY;
121+
122+
// Lock aspect ratio to 1:1.
123+
if (shiftKey) {
124+
const smallerSize = Math.min(Math.abs(xDelta), Math.abs(yDelta));
125+
xDelta = bound(-smallerSize, xDelta, smallerSize);
126+
yDelta = bound(-smallerSize, yDelta, smallerSize);
127+
}
128+
129+
// Based on which dragger is moving, update the correct coordinates.
130+
switch(draggerIndex) {
131+
case 0:
132+
x0 = x1 - xDelta;
133+
y0 = y1 - yDelta;
134+
break;
135+
case 1:
136+
x1 = x0 - xDelta;
137+
y0 = y1 - yDelta;
138+
break;
139+
case 2:
140+
x1 = x0 - xDelta;
141+
y1 = y0 - yDelta;
142+
break;
143+
case 3:
144+
x0 = x1 - xDelta;
145+
y1 = y0 - yDelta;
146+
break;
147+
}
148+
149+
updateSelected();
150+
event.preventDefault();
151+
}
152+
153+
function resetSelection() {
154+
x0 = 0
155+
y0 = 0;
156+
x1 = x0 + 96;
157+
y1 = y0 + 96;
158+
159+
updateSelected();
160+
notifySelected();
161+
}
162+
163+
targetImg.addEventListener('load', () => {
164+
resetSelection();
165+
});
166+
167+
window.addEventListener('mousedown', event => {
168+
const target = event.target.closest('.target');
169+
170+
if (!target) {
171+
return;
172+
}
173+
174+
const initialRect = targetImg.getBoundingClientRect();
175+
const x = event.x - initialRect.left;
176+
const y = event.y - initialRect.top;
177+
178+
dragger = draggers[2];
179+
mousedown = true;
180+
x0 = x;
181+
y0 = y;
182+
x1 = x0;
183+
y1 = y0;
184+
185+
updateSelected();
186+
event.preventDefault();
187+
});
188+
189+
window.addEventListener('mouseup', event => {
190+
if (!mousedown) {
191+
return;
192+
}
193+
194+
mousedown = false;
195+
notifySelected();
196+
});
197+
198+
199+
window.addEventListener('keydown', event => {
200+
if (!mousedown) {
201+
return;
202+
}
203+
204+
updateCoordinates(event.shiftKey);
205+
});
206+
207+
window.addEventListener('keyup', event => {
208+
if (!mousedown) {
209+
return;
210+
}
211+
212+
updateCoordinates(event.shiftKey);
213+
});
214+
215+
window.addEventListener('mousemove', event => {
216+
if (!mousedown) {
217+
return;
218+
}
219+
220+
lastX = event.x;
221+
lastY = event.y;
222+
223+
updateCoordinates(event.shiftKey);
224+
});
225+
226+
setTimeout(() => {
227+
resetSelection();
228+
});

docs/tools/img-cropper/dropper.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Copyright 2019 The AMP HTML Authors. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS-IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const dropArea = document.querySelector('.drop');
18+
const imgContainer = document.querySelector('.src-img-container');
19+
const origImg = document.querySelector('.src-img-container img');
20+
21+
function onDragEnter(e) {
22+
dropArea.setAttribute('drag-over', '');
23+
24+
window.addEventListener('dragend', function onDragEnd(e) {
25+
window.removeEventListener('dragend');
26+
dropArea.removeAttribute('drag-over');
27+
});
28+
29+
e.stopPropagation();
30+
e.preventDefault();
31+
}
32+
33+
function onDragOver(e) {
34+
e.stopPropagation();
35+
e.preventDefault();
36+
}
37+
38+
function onDragLeave(e) {
39+
if (e.target == dropArea || !dropArea.contains(e.target)) {
40+
dropArea.removeAttribute('drag-over');
41+
}
42+
43+
44+
e.stopPropagation();
45+
e.preventDefault();
46+
}
47+
48+
/**
49+
* Handles a drop event, which could potentially be an image URL (dragged from
50+
* a website) or a File (dragged from a Desktop).
51+
* @param {!Event} e
52+
*/
53+
function onDrop(e) {
54+
dropArea.removeAttribute('drag-over');
55+
56+
e.stopPropagation();
57+
e.preventDefault();
58+
59+
const dt = e.dataTransfer;
60+
const url = dt.getData('URL');
61+
if (url) {
62+
updateSrc(url);
63+
} else if (dt.files) {
64+
[...dt.files]
65+
.filter(f => f.type.startsWith('image/'))
66+
.filter((f, i) => i == 0)
67+
.forEach(f => {
68+
readImg(f);
69+
});
70+
}
71+
72+
return false;
73+
}
74+
75+
/**
76+
* Updates the src of the image form selection.
77+
* @param {string} src The src to use.
78+
*/
79+
function updateSrc(src) {
80+
origImg.src = src;
81+
window.dispatchEvent(new CustomEvent('img-change'));
82+
}
83+
84+
/**
85+
* Reads a file, then sets the src of the image using the contents as a data-url.
86+
* @param {!File} file The file to read.
87+
*/
88+
function readImg(file) {
89+
const reader = new FileReader();
90+
reader.onload = function(){
91+
const dataUrl = reader.result;
92+
updateSrc(dataUrl);
93+
};
94+
reader.readAsDataURL(file);
95+
}
96+
97+
dropArea.addEventListener('dragenter', onDragEnter, true);
98+
dropArea.addEventListener('dragover', onDragOver, true);
99+
dropArea.addEventListener('dragleave', onDragLeave, true);
100+
dropArea.addEventListener('drop', onDrop, true);

0 commit comments

Comments
 (0)