Skip to content

Commit 03fd113

Browse files
author
Sepand Parhami
authored
Add logic for manually specifying the initial crop rect. (#18)
This allows developers to specify the cropping rect rather than using the bounds of the `<img>` element. So now you can do something like: ```html <div style="overflow: hidden"> <img src="..." style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(2);"> </div> ``` In this example, you will have something that renders like: ``` ________________ | ____ | | | | | | | | | | ‾‾‾‾ | ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ ``` where the inner rectangle represents the cropping div, and the outer rectangle represents the `<img>` boundaries. With this, you can zoom in on a portion of an image in a way that `object-position` does not allow you to. As an alternative, you could use `object-fit: none`, `object-position`, and `transform: scale`, but this does not work well when trying to responsively size images (e.g. using the `padding-bottom` trick). Without this change, the animation is off as the image portion escaping the overflow container is visible at the start/end on the first frame of the animation (instead of the crop growing),
1 parent 6a3cce2 commit 03fd113

File tree

10 files changed

+498
-13
lines changed

10 files changed

+498
-13
lines changed

compile/externs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ let Curve;
1515
* srcImg: HTMLImageElement,
1616
* targetImg: HTMLImageElement,
1717
* srcImgRect: ClientRect,
18+
* srcCropRect: ClientRect,
1819
* targetImgRect: ClientRect,
20+
* targetCropRect: ClientRect,
1921
* curve: Curve,
2022
* styles: Object,
2123
* keyframesNamespace: string,

docs/demo/zoom-crop/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Like the hero demo, but starting zoomed in on part of the image. This uses an outer cropping container to zoom in on part of the image.

docs/demo/zoom-crop/boats.jpg

194 KB
Loading

docs/demo/zoom-crop/index.css

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright 2018 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+
body {
18+
overflow: hidden;
19+
}
20+
21+
.small {
22+
position: relative;
23+
flex-shrink: 0;
24+
width: 96px;
25+
height: 96px;
26+
overflow: hidden;
27+
}
28+
29+
.small .hero {
30+
position: absolute;
31+
top: 0;
32+
left: 0;
33+
width: 100%;
34+
height: 100%;
35+
object-fit: cover;
36+
}
37+
38+
.large {
39+
max-width: 100%;
40+
object-fit: cover;
41+
}
42+
43+
.related {
44+
display: flex;
45+
align-items: center;
46+
outline: none;
47+
}
48+
49+
.desc {
50+
padding: 0 12px;
51+
}
52+
53+
.page {
54+
position: absolute;
55+
top: 0;
56+
left: 0;
57+
right: 0;
58+
bottom: 0;
59+
overflow-x: hidden;
60+
overflow-y: auto;
61+
max-width: 30em;
62+
margin: auto;
63+
padding: 12px;
64+
}
65+
66+
.content-container {
67+
/** Used by the animation to know how to position. */
68+
position: relative;
69+
}
70+
71+
[transition] .content {
72+
opacity: 0;
73+
animation-name: fadeIn;
74+
animation-duration: 200ms;
75+
animation-delay: 100ms;
76+
animation-timing-function: ease-in;
77+
animation-fill-mode: forwards;
78+
}
79+
80+
[transition] .hero {
81+
visibility: hidden;
82+
}
83+
84+
@keyframes fadeIn {
85+
from {
86+
opacity: 0;
87+
}
88+
89+
to {
90+
opacity: 1;
91+
}
92+
}

docs/demo/zoom-crop/index.html

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!--
2+
Copyright 2018 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+
<html>
18+
<head>
19+
<meta name="viewport" content="width=device-width, initial-scale=1">
20+
<link rel="stylesheet" href="index.css">
21+
<script type="module" crossorigin src="index.js" defer></script>
22+
</head>
23+
<body>
24+
<div class="page" id="first">
25+
<div class="content-container">
26+
<div class="content">
27+
<p>
28+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
29+
</p><p>
30+
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
31+
</p>
32+
33+
<div class="related" onclick="toggle(event, 'second')" tabindex="-1">
34+
<div class="small hero-crop">
35+
<img class="hero" aria-describedby="desc" src="boats.jpg" style="transform: scale(2); transform-origin: center bottom;">
36+
</div>
37+
<div id="desc" class="desc">
38+
Some boats on lake Tahoe. Click on me to toggle.
39+
</div>
40+
</div>
41+
42+
<p>
43+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
44+
</p><p>
45+
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
46+
</p>
47+
</div>
48+
</div>
49+
</div>
50+
51+
<div class="page" id="second" hidden>
52+
<div class="content-container">
53+
<div class="content">
54+
<img class="large hero" onclick="toggle(event, 'first')" src="boats.jpg">
55+
<p>
56+
The image changes size along with the crop. The image above is 16x9,
57+
matching the image's natural dimensions. The smaller version was 1x1.
58+
As the image expands, the sides of the image will gradually become
59+
visible.
60+
61+
Click on the image above to toggle back.
62+
</p>
63+
<p>
64+
Lorem ipsum dolor sit amet, consectetaur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
65+
</p>
66+
<p>
67+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
68+
</p><p>
69+
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
70+
</p>
71+
</div>
72+
</div>
73+
</div>
74+
</body>
75+
</html>

docs/demo/zoom-crop/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright 2018 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+
import {prepareImageAnimation} from '../../../dist/index.js';
18+
19+
const duration = 600;
20+
const curve = {x1: 0.8, y1: 0, x2: 0.2, y2: 1};
21+
const styles = {
22+
animationDuration: `${duration}ms`,
23+
};
24+
25+
window.toggle = function(event, targetId) {
26+
const current = event.currentTarget.closest('.page');
27+
const target = document.getElementById(targetId);
28+
const srcImg = current.querySelector('.hero');
29+
const srcCrop = current.querySelector('.hero-crop') || srcImg;
30+
const targetImg = target.querySelector('.hero');
31+
const targetCrop = target.querySelector('.hero-crop') || targetImg;
32+
// We do the transition within the target page. This will make sure that if
33+
// the user scrolls during the animation, that the image still animates to
34+
// the correct location. Note that this element has `position: relative`
35+
// so that the animation can position correctly.
36+
const transitionContainer = target.querySelector('.content-container');
37+
38+
target.hidden = false;
39+
40+
const {
41+
applyAnimation,
42+
cleanupAnimation,
43+
} = prepareImageAnimation({
44+
transitionContainer,
45+
srcImg,
46+
srcCropRect: srcCrop.getBoundingClientRect(),
47+
targetImg,
48+
targetCropRect: targetCrop.getBoundingClientRect(),
49+
styles,
50+
curve,
51+
});
52+
53+
target.setAttribute('transition', '');
54+
current.hidden = true;
55+
applyAnimation();
56+
57+
setTimeout(() => {
58+
target.removeAttribute('transition');
59+
cleanupAnimation();
60+
}, duration + 100);
61+
}

src/intermediate-img.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,33 +41,37 @@ import {getPositioningTranslate} from './object-position.js';
4141
export function createIntermediateImg(
4242
srcImg: HTMLImageElement,
4343
srcImgRect: ClientRect = srcImg.getBoundingClientRect(),
44+
srcCropRect: ClientRect = srcImgRect,
4445
imagePosition: string = getComputedStyle(srcImg).getPropertyValue('object-position'),
4546
imageDimensions: Size = getRenderedDimensions(srcImg, srcImgRect),
4647
): {
4748
translateElement: HTMLElement,
4849
scaleElement: HTMLElement,
4950
counterScaleElement: HTMLElement,
51+
cropPositionContainer: HTMLElement,
5052
imgContainer: HTMLElement,
5153
img: HTMLImageElement,
5254
} {
5355
const positioningTranslate = getPositioningTranslate(imagePosition, srcImgRect, imageDimensions);
5456
const translateElement = document.createElement('div');
5557
const scaleElement = document.createElement('div');
5658
const counterScaleElement = document.createElement('div');
59+
const cropPositionContainer = document.createElement('div');
5760
const imgContainer = document.createElement('div');
5861
const img = <HTMLImageElement>srcImg.cloneNode(true);
5962

6063
img.className = '';
6164
img.style.cssText = '';
6265
imgContainer.appendChild(img);
63-
counterScaleElement.appendChild(imgContainer);
66+
cropPositionContainer.appendChild(imgContainer);
67+
counterScaleElement.appendChild(cropPositionContainer);
6468
scaleElement.appendChild(counterScaleElement);
6569
translateElement.appendChild(scaleElement);
6670

6771
Object.assign(scaleElement.style, {
6872
'overflow': 'hidden',
69-
'width': `${srcImgRect.width}px`,
70-
'height': `${srcImgRect.height}px`,
73+
'width': `${srcCropRect.width}px`,
74+
'height': `${srcCropRect.height}px`,
7175
});
7276

7377
Object.assign(imgContainer.style, {
@@ -84,6 +88,7 @@ export function createIntermediateImg(
8488
translateElement,
8589
scaleElement,
8690
counterScaleElement,
91+
cropPositionContainer,
8792
imgContainer,
8893
img,
8994
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright 2018 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+
import {Curve, curveToString} from '../bezier-curve-utils.js';
18+
19+
/**
20+
* Prepares an animation for position (i.e. for object-position). This function
21+
* sets up the animation by setting the appropriate style properties on the
22+
* desired Element. The returned style text needs to be inserted for the
23+
* animation to run.
24+
* @param options
25+
* @param options.element The element to apply the position to.
26+
* @param options.largerRect
27+
* rects.
28+
* @param options.largerCropRect
29+
* @param options.smallerRect
30+
* @param options.smallerCropRect
31+
* @param options.curve The timing curve for the scaling.
32+
* @param options.style The styles to apply to `element`.
33+
* @param options.keyframesPrefix A prefix to use for the generated
34+
* keyframes to ensure they do not clash with existing keyframes.
35+
* @param options.toLarger Whether or not `largerRect` / `largerCropRect` are
36+
* the positions are we are animating to.
37+
* @return CSS style text to perform the animation.
38+
*/
39+
export function prepareCropPositionAnimation({
40+
element,
41+
largerRect,
42+
largerCropRect,
43+
smallerRect,
44+
smallerCropRect,
45+
curve,
46+
styles,
47+
keyframesPrefix,
48+
toLarger,
49+
} : {
50+
element: HTMLElement,
51+
largerRect: ClientRect,
52+
largerCropRect: ClientRect,
53+
smallerRect: ClientRect,
54+
smallerCropRect: ClientRect,
55+
curve: Curve,
56+
styles: Object,
57+
keyframesPrefix: string,
58+
toLarger: boolean,
59+
}): string {
60+
const curveString = curveToString(curve);
61+
const keyframesName = `${keyframesPrefix}-crop-position`;
62+
63+
const largerTranslate = {
64+
top: largerRect.top - largerCropRect.top,
65+
left: largerRect.left - largerCropRect.left,
66+
};
67+
const smallerTranslate = {
68+
top: smallerRect.top - smallerCropRect.top,
69+
left: smallerRect.left - smallerCropRect.left,
70+
};
71+
const startTranslate = toLarger ? smallerTranslate : largerTranslate;
72+
const endTranslate = toLarger ? largerTranslate : smallerTranslate;
73+
74+
Object.assign(element.style, styles, {
75+
'willChange': 'transform',
76+
'animationName': keyframesName,
77+
'animationTimingFunction': curveString,
78+
'animationFillMode': 'forwards',
79+
});
80+
81+
return `
82+
@keyframes ${keyframesName} {
83+
from {
84+
transform: translate(${startTranslate.left}px, ${startTranslate.top}px);
85+
}
86+
87+
to {
88+
transform: translate(${endTranslate.left}px, ${endTranslate.top}px);
89+
}
90+
}
91+
`;
92+
}

0 commit comments

Comments
 (0)