Skip to content

Commit a5d50e4

Browse files
author
Sepand Parhami
authored
Add docs for prepareImageAnimation (#9)
1 parent d039692 commit a5d50e4

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

docs/prepare-image-animation.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
## `prepareImageAnimation`
2+
3+
Prepares an animation for an image from one size, location and crop to another.
4+
5+
### Typical Usage
6+
7+
```javascript
8+
const duration = 400;
9+
const {
10+
applyAnimation,
11+
cleanupAnimation,
12+
} = prepareImageAnimation({
13+
srcImg,
14+
targetImg,
15+
styles: {
16+
animationDuration: `${duration}ms`,
17+
},
18+
});
19+
20+
srcImg.style.visibility = 'hidden';
21+
targetImg.style.visibility = 'hidden';
22+
applyAnimation();
23+
setTimeout(() => {
24+
targetImg.style.visibility = 'visible';
25+
cleanupAnimation();
26+
}, duration);
27+
```
28+
29+
### Demos
30+
31+
* [Hero animation](./demo/hero)
32+
* [Lightbox](./demo/lightbox)
33+
* [Image gallery](./demo/gallery)
34+
35+
### How `prepareImageAnimation` Works
36+
37+
The animation is done by creating a temporary `<img>` element that is animated between the source and the target. Once the animation is completed, the temporary `<img>` is removed. The animation is done using `position: absolute`, to allow the image to move as the user scrolls.
38+
39+
In order to animate the crop, the function looks at how the source and target images are rendered using the size and [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property. It then animates between the two states, which may cause the cropping to change as the animation proceeds. See the [hero animation demo](./demo/hero) for an example of this in action.
40+
41+
The animation is first prepared, then applied and finally cleaned up. The creation and application are two different steps, which can be useful if you want to avoid [layout thrashing](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing#avoid_forced_synchronous_layouts) using a library like [fastdom](https://github.com/wilsonpage/fastdom).
42+
43+
### Function signature
44+
45+
```javascript
46+
function prepareImageAnimation({
47+
transitionContainer = document.body,
48+
styleContainer = document.head!,
49+
srcImg,
50+
targetImg,
51+
srcImgRect = srcImg.getBoundingClientRect(),
52+
targetImgRect = targetImg.getBoundingClientRect(),
53+
curve = EASE_IN_OUT,
54+
styles,
55+
keyframesNamespace = 'img-transform',
56+
} : {
57+
transitionContainer: HTMLElement,
58+
styleContainer: Element|Document|DocumentFragment,
59+
srcImg: HTMLImageElement,
60+
targetImg: HTMLImageElement,
61+
srcImgRect?: ClientRect,
62+
targetImgRect?: ClientRect,
63+
curve?: Curve,
64+
styles: Object,
65+
keyframesNamespace?: string,
66+
}) : {
67+
applyAnimation: () => void,
68+
cleanupAnimation: () => void,
69+
}
70+
```
71+
72+
### Return Value
73+
74+
#### `applyAnimation`
75+
76+
Applies the animation by inserting the temporary transition `<img>` into the `transitionContainer` as well as inserting a dynamically generated stylesheet into `styleContainer`.
77+
78+
#### `cleanupAnimation`
79+
80+
Undoes the effects of `applyAnimation`.
81+
82+
### Parameters
83+
84+
#### `transitionContainer`
85+
86+
This option defaults to `document.body` and is where the the animating `<img>` is placed. Two cases where you might not want this to be the body are:
87+
88+
1. The body is not the scrolling container.
89+
2. The body is the scrolling container, but is not currently scrolling.
90+
91+
When the body is not the scrolling container, you will want to place the animating `<img>` somewhere in the scrolling container. As an exmaple, the [hero animation demo](./demo/hero) places the transition image on the newly active page. The structure looks like:
92+
93+
```html
94+
<div class="page" style="position: absolute; overflow-y: auto">
95+
<div class="content-container" style="position: relative;">
96+
… content
97+
</div>
98+
</div>
99+
```
100+
101+
The demo uses `.content-container` as `transitionContainer`. Since the `transitionContainer` moves as the user scrolls, the animation moves in sync. Note that the `transitionContainer` may actually be a descendent of `content-container`, as `prepareImageAnimation` looks for the first positioned ancestor.
102+
103+
#### `styleContainer`
104+
105+
This defaults to `document.head` and is where generated CSS for the animation is placed. If you want the animation to be placed within [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) (i.e. specifying a `transitionContainer` within a `ShadowRoot`), then you will want the `ShadowRoot` to be the `styleContainer`.
106+
107+
#### `srcImg`
108+
109+
An `<img>` to animate from. This is used to determine the position, size, and the `object-fit` property to start the animation with.
110+
111+
112+
#### `targetImg`
113+
114+
An `<img>` to animate to. This is used to determine the position, size, and the `object-fit` property to end the animation with.
115+
116+
#### `srcImgRect`
117+
118+
Defaults to `srcImg.getBoundingClientRect()`. If the `srcImg` is not laid out at the time you call `prepareImageAnimation`, you will want to capture the `ClientRect` beforehand and provide it to the call.
119+
120+
One situtation this might be useful is if you are doing an animation between pages, where the content is in the `body` itself rather than in a separate scrolling container. For example. consider the following page structure:
121+
122+
```html
123+
<body>
124+
<div class="page">
125+
126+
</div>
127+
<div class="page" hidden>
128+
<h1>Some title that might wrap depending on the viewport width</h1>
129+
<img class="hero" >
130+
</div>
131+
</body>
132+
```
133+
134+
To figure out where the `hero` will be positioned, we need to layout the target page (e.g. by adding `hidden` to the current page and removing it from the target page). However, hiding the current page will mean `prepareImageAnimation` will no longer know where to start the animation. By providing `srcImgRect`, the animation can know where to start from.
135+
136+
#### `targetImgRect`
137+
138+
Defaults to `targetImg.getBoundingClientRect()`. If you know where the `targetImg` will be rendered, but you have not laid out the containing content, you can provide it to `prepareImageAnimation`. You can use this to avoid a forced layout in some situations, for example in the [hero animation demo](./demo/hero), we do something like:
139+
140+
```javascript
141+
// Layout the the target so that we know where targetImg is
142+
target.hidden = false;
143+
144+
// Forced style calc + layout when we go to measure things
145+
const {
146+
applyAnimation,
147+
cleanupAnimation,
148+
} = prepareImageAnimation(…);
149+
150+
// Regular style calc + layout for mutations
151+
current.hidden = true;
152+
applyAnimation();
153+
```
154+
155+
The forced style calculation caused by `prepareImageAnimation` can be avoided if you already know where `targetImg` will be positioned. Note that in this case, you will still need to provide a `targetImg` to the function so that the animation knows the `object-fit` property to animate to.
156+
157+
#### `curve`
158+
159+
This option defaults to the built-in `ease-in-out` transition timing function (`{x1: 0.42, y1: 0, x2: 0.58, y2: 1}`). This is an object with the control points for a [`cubic-bezier()`](https://developer.mozilla.org/en-US/docs/Web/CSS/single-transition-timing-function#The_cubic-bezier()_class_of_timing_functions) curve and is used to determine the animation progress for the position, size and crop at any given time.
160+
161+
#### `styles`
162+
163+
An object of styles to apply to the animating elements. At the minimum, this should include `animationDuration`. Other useful properties may include `animationDelay` (if you want to synchronize this with another animation, which should start earlier) and `z-index`.
164+
165+
#### `keyframesNamespace`
166+
167+
This option defaults to `'img-transform'`. In order to play the animation, CSS keyframes need to be dynamically created. The prefix is used to make sure that the generated names will not colide with any other keyframes present. It is very unlikely that this needs to be specified.
168+
169+
### Using different resolution images
170+
171+
If you are doing an animation from a smaller image to a larger image, you may want to use a low resolution image for the smaller image to make it load faster and save bandwidth. The [image gallery demo](./demo/gallery) outlines an approach to accomplish this. In short, you will want to perform the following steps:
172+
173+
1. Start preloading the higher resolution image (e.g. on `mousedown`/`touchstart` or when starting the animation)
174+
1. Set the `src` for `targetImg` (either via `src` or `srcset`/`sizes`) to the lower resolution image
175+
1. Perform the image animation
176+
1. Once the higher resolution image has finished downloaded, set the `src` for `targetImg` to the higher resolution image
177+
178+
The [image gallery demo code](./demo/gallery/index.js) implements this approach using `srcset`.

0 commit comments

Comments
 (0)