Skip to content

Commit 817768f

Browse files
author
Sepand Parhami
authored
Merge pull request #1 from sparhami/code
Initial commit of code
2 parents 6c36629 + 14f0ed3 commit 817768f

18 files changed

+2083
-1
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,19 @@ Helps with writing tests for animations by pausing then and allowing control of
1414
the progress of animations on the page. You can pause an animation part way
1515
through and do a screenshot based test or simply validate the position or
1616
dimensions of elements.
17+
18+
## Developing
19+
20+
### Build
21+
22+
```shell
23+
npm run build
24+
npm run build-watch
25+
```
26+
27+
### Test
28+
29+
```shell
30+
npm run test
31+
npm run test-watch
32+
```

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {prepareImageAnimation} from './src/transform-img/index.js';

src/bezier-curve-utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
export interface Curve {
18+
x1: number,
19+
y1: number,
20+
x2: number,
21+
y2: number,
22+
}
23+
24+
/**
25+
* A string representation of the curve that can be used as an
26+
* `animation-timing-function`.
27+
* @param curve The curve to conver.
28+
* @return A string in the form of 'cubic-bezier(x1, y1, x2, y2)'.
29+
*/
30+
export function curveToString(curve: Curve): string {
31+
return `cubic-bezier(${curve.x1}, ${curve.y1}, ${curve.x2}, ${curve.y2})`;
32+
}
33+
34+
/**
35+
* Gets the x/y value for the given control points for a given value of t. The
36+
* first control point is always zero and the fourth is always one.
37+
* @param c1 The second control point.
38+
* @param c2 The third control point.
39+
* @param t
40+
* @return The value at t.
41+
*/
42+
export function getCubicBezierCurveValue(
43+
c1: number, c2: number, t: number): number {
44+
const t_2 = t * t;
45+
const t_3 = t_2 * t;
46+
// Formula for 4 point bezier curve with c0 = 0 and c3 = 1.
47+
return (3 * (t - 2 * t_2 + t_3) * c1) +
48+
(3 * (t_2 - t_3) * c2) + (t_3);
49+
}
50+

src/img-dimensions.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
/**
18+
* @fileoverview Provides a function to calculate the dimensions of the
19+
* rendered image inside of an `<img>` Element. For example, if you have an
20+
* `<img>` with `object-fit: contain` and an image that is portrait inside of
21+
* an `<img>` with landscape dimensions, you will have something looks like:
22+
* _____________
23+
* | | | |
24+
* | i | r | i |
25+
* |___|_____|___|
26+
*
27+
* Where the area denoted by `r` is the rendered image and the areas denoted
28+
* by `i` are extra spacing on either side of the rendered image to center it
29+
* within the containing `<img>` Element.
30+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
31+
*/
32+
33+
export interface Size {
34+
width: number,
35+
height: number,
36+
}
37+
38+
/**
39+
* Constrains the size of the image to the given width and height. This either
40+
* caps the width or the height depending on the aspect ratio of original img
41+
* and if we want to have the smaller or larger dimension fit the container.
42+
* @param naturalSize The natural dimensions of the image.
43+
* @param containerSize The size of the container we want to render the image
44+
* in.
45+
* @param toMin If we should cap the smaller dimension of the image to fit the
46+
* container (`object-fit: cover`) or the larger dimension
47+
* (`object-fit: contain`).
48+
* @return The Size that the image should be rendered as.
49+
*/
50+
function constrain(
51+
naturalSize: Size, containerSize: Size, toMin: boolean): Size {
52+
const elAspectRatio = containerSize.width / containerSize.height;
53+
const naturalAspectRatio = naturalSize.width / naturalSize.height;
54+
55+
if (naturalAspectRatio > elAspectRatio !== toMin) {
56+
return {
57+
width: containerSize.height * naturalAspectRatio,
58+
height: containerSize.height,
59+
};
60+
}
61+
62+
return {
63+
width: containerSize.width,
64+
height: containerSize.width / naturalAspectRatio,
65+
};
66+
}
67+
68+
function getDimensionsForObjectFitCover(
69+
naturalSize: Size, containerSize: Size): Size {
70+
return constrain(naturalSize, containerSize, false);
71+
}
72+
73+
function getDimensionsForObjectFitContain(
74+
naturalSize: Size, containerSize: Size): Size {
75+
return constrain(naturalSize, containerSize, true);
76+
}
77+
78+
function getDimensionsForObjectFitFill(containerSize: Size): Size {
79+
return containerSize;
80+
}
81+
82+
function getDimensionsForObjectFitNone(naturalSize: Size): Size {
83+
return naturalSize;
84+
}
85+
86+
function getDimensionsForObjectFitScaleDown(
87+
naturalSize: Size, containerSize: Size): Size {
88+
const noneSize = getDimensionsForObjectFitNone(naturalSize);
89+
const containSize = getDimensionsForObjectFitContain(
90+
naturalSize, containerSize);
91+
92+
// Since both have the same aspect ratio, we can simply take the smaller
93+
// dimension for both.
94+
return {
95+
width: Math.min(noneSize.width, containSize.width),
96+
height: Math.min(noneSize.height, containSize.height),
97+
};
98+
}
99+
100+
/**
101+
* Gets the dimensions for the rendered "image" rather than the container
102+
* that constrains the size with the CSS `object-fit` property.
103+
* @param img The HTMLImageElement
104+
* @param containerSize The size of the container element.
105+
* @return The width/height of the "actual" image.
106+
*/
107+
export function getRenderedDimensions(
108+
img: HTMLImageElement, containerSize: Size): Size {
109+
const objectFit = getComputedStyle(img).getPropertyValue('object-fit');
110+
const naturalSize = {
111+
width: img.naturalWidth,
112+
height: img.naturalHeight,
113+
};
114+
115+
switch(objectFit) {
116+
case 'cover':
117+
return getDimensionsForObjectFitCover(naturalSize, containerSize);
118+
case 'contain':
119+
return getDimensionsForObjectFitContain(naturalSize, containerSize);
120+
case 'fill':
121+
return getDimensionsForObjectFitFill(containerSize);
122+
case 'none':
123+
return getDimensionsForObjectFitNone(naturalSize);
124+
case 'scale-down':
125+
return getDimensionsForObjectFitScaleDown(naturalSize, containerSize);
126+
default:
127+
throw new Error(`object-fit: ${objectFit} not supported`);
128+
}
129+
}

src/intermdediate-img.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
/**
18+
* @fileoverview This file is used to create an image for use in an `<img>` to
19+
* `<img>` animation. It is implemented in a way to allow for animating the
20+
* cropping of the rendered image. Once the animation is completed, the
21+
* intermediate image can be removed to show the destination `<img>` instead.
22+
*/
23+
24+
import {Size, getRenderedDimensions} from './img-dimensions.js';
25+
26+
/**
27+
* Creates a replacement for a given img, which should render the same as the
28+
* source img, but implemented with a cropping container and and img using
29+
* `object-fit: fill`. This can be used to implement a transition of the image.
30+
* The crop can be transitioned by scaling up the container while scaling down
31+
* the image by the inverse amount.
32+
* @param srcImg The source img.
33+
* @param srcImgRect The rect for `srcImg`. Can be provided if already
34+
* measured.
35+
* @param imageDimensions The dimensions for the rendered image. Can be
36+
* provided if already measured.
37+
* @return The replacement container along with structural information.
38+
*/
39+
export function createItermediateImg(
40+
srcImg: HTMLImageElement,
41+
srcImgRect: ClientRect = srcImg.getBoundingClientRect(),
42+
imageDimensions: Size = getRenderedDimensions(srcImg, srcImgRect),
43+
): {
44+
translateElement: HTMLElement,
45+
scaleElement: HTMLElement,
46+
counterScaleElement: HTMLElement,
47+
img: HTMLImageElement,
48+
} {
49+
const translateElement = document.createElement('div');
50+
const scaleElement = document.createElement('div');
51+
const counterScaleElement = document.createElement('div');
52+
const imgWrapper = document.createElement('div');
53+
const img = <HTMLImageElement>srcImg.cloneNode(true);
54+
img.className = '';
55+
img.style.cssText = '';
56+
imgWrapper.appendChild(img);
57+
counterScaleElement.appendChild(imgWrapper);
58+
scaleElement.appendChild(counterScaleElement);
59+
translateElement.appendChild(scaleElement);
60+
61+
Object.assign(scaleElement.style, {
62+
'display': 'flex',
63+
'overflow': 'hidden',
64+
'alignItems': 'center',
65+
'justifyContent': 'center',
66+
'width': `${srcImgRect.width}px`,
67+
'height': `${srcImgRect.height}px`,
68+
});
69+
70+
Object.assign(img.style, {
71+
'display': 'block',
72+
'width': `${imageDimensions.width}px`,
73+
'height': `${imageDimensions.height}px`,
74+
});
75+
76+
return {
77+
translateElement,
78+
scaleElement,
79+
counterScaleElement,
80+
img,
81+
};
82+
}

0 commit comments

Comments
 (0)