Skip to content

Commit 878900c

Browse files
committed
feat(styles): translate container on hover, refactor static styles
1 parent e5fd334 commit 878900c

File tree

3 files changed

+102
-92
lines changed

3 files changed

+102
-92
lines changed

example/index.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ render(
2020
<div style={style}>
2121
<LayeredImage
2222
layers={[1, 2, 3].map(index => `./static/images/dazzle-layer-${index}.png`)}
23-
aspectRatio={0.6}
24-
lightOpacity={0.25}
2523
shadowColor="#1f2933"
26-
shadowOpacity={0.55}
2724
style={{ width: 400 }}
2825
/>
2926
</div>,

lib/LayeredImage.tsx

Lines changed: 53 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import * as React from "react";
2-
import * as TypeStyle from "typestyle";
2+
import { classes as classNames } from "typestyle";
33

4+
import { default as classes } from "./classes";
45
import { clamp, isFunction } from "./utils";
56

67
export enum Interaction {
78
None = "NONE",
9+
Resize = "RESIZE",
810
Hover = "HOVER",
911
Active = "ACTIVE",
10-
Resize = "RESIZE",
1112
}
1213

1314
export interface ILayeredImageProps extends React.HTMLProps<HTMLDivElement> {
1415
layers: Array<string>;
15-
aspectRatio: number;
16+
aspectRatio?: number;
1617
borderRadius?: React.CSSProperties["borderRadius"];
1718
transitionDuration?: React.CSSProperties["transitionDuration"];
1819
transitionTimingFunction?: React.CSSProperties["transitionTimingFunction"];
@@ -41,14 +42,14 @@ interface ILayeredImageStyles {
4142

4243
export default class LayeredImage extends React.Component<ILayeredImageProps, ILayeredImageState> {
4344
public static defaultProps: Partial<ILayeredImageProps> = {
44-
aspectRatio: 0.75,
45-
borderRadius: 5,
45+
aspectRatio: 16 / 10,
46+
borderRadius: 6,
4647
transitionDuration: 0.2,
4748
transitionTimingFunction: "ease-out",
4849
lightColor: "#fff",
49-
lightOpacity: 0.1,
50+
lightOpacity: 0.2,
5051
shadowColor: "#000",
51-
shadowOpacity: 0.4,
52+
shadowOpacity: 0.6,
5253
};
5354

5455
public state: ILayeredImageState = {
@@ -89,33 +90,37 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
8990
style,
9091
} = this.props;
9192
const { width, loaded } = this.state;
93+
const staticStyles = this.getStaticStyles();
9294
const styles: ILayeredImageStyles = {
9395
root: {
94-
borderRadius: borderRadius,
96+
borderRadius,
9597
transform: `perspective(${width * 3}px)`,
98+
...staticStyles.root,
9699
},
97100
container: {
98-
borderRadius: borderRadius,
99-
transition: `transform ${transitionDuration}s ${transitionTimingFunction}`,
101+
borderRadius,
102+
transitionTimingFunction,
103+
...staticStyles.container,
100104
},
101105
layers: {
102-
borderRadius: borderRadius,
106+
borderRadius,
107+
...staticStyles.layers,
103108
},
104109
layer: {
105-
transition: `transform ${transitionDuration}s ${transitionTimingFunction},
106-
opacity ${transitionDuration * 3}s ${transitionTimingFunction}`,
110+
transitionDuration: `${transitionDuration}s, ${transitionDuration * 3}s`,
111+
transitionTimingFunction,
112+
...staticStyles.layer,
107113
},
108114
light: {
109-
borderRadius: borderRadius,
110-
backgroundImage: `linear-gradient(180deg, ${lightColor} 0%, transparent 80%)`,
115+
borderRadius,
111116
opacity: lightOpacity,
117+
...staticStyles.light,
112118
},
113119
shadow: {
114-
borderRadius: borderRadius * 2,
115-
boxShadow: `0 8px 20px ${shadowColor}, 0 2px 4px ${shadowColor}`,
120+
borderRadius,
116121
opacity: shadowOpacity,
117-
transition: `box-shadow ${transitionDuration}s ${transitionTimingFunction},
118-
opacity ${transitionDuration}s ${transitionTimingFunction}`,
122+
transitionTimingFunction,
123+
...staticStyles.shadow,
119124
},
120125
};
121126

@@ -129,7 +134,7 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
129134
onTouchStart={this.handleTouchInteraction(Interaction.Hover)}
130135
onTouchMove={this.handleTouchInteraction(Interaction.Hover)}
131136
onTouchEnd={this.handleInteractionEnd}
132-
className={TypeStyle.classes(classes.root, className)}
137+
className={classNames(classes.root, className)}
133138
style={{ ...styles.root, ...style }}
134139
ref={this.refHandlers.root}
135140
>
@@ -211,11 +216,32 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
211216
this.elements.container.offsetWidth ||
212217
this.elements.container.clientWidth ||
213218
this.elements.container.scrollWidth;
214-
const height = Math.round(width * aspectRatio);
219+
const height = Math.round(width / aspectRatio);
215220

216221
return { width, height };
217222
};
218223

224+
private getStaticStyles = (): ILayeredImageStyles => {
225+
const { transitionDuration, lightColor, shadowColor } = this.props;
226+
227+
return {
228+
container: {
229+
transform: "none",
230+
transitionDuration: `${transitionDuration}s`,
231+
},
232+
layer: {
233+
transform: "none",
234+
},
235+
light: {
236+
backgroundImage: `linear-gradient(180deg, ${lightColor} 0%, transparent 80%)`,
237+
},
238+
shadow: {
239+
boxShadow: `0 10px 30px ${shadowColor}, 0 6px 10px ${shadowColor}`,
240+
transitionDuration: `${transitionDuration}s`,
241+
},
242+
};
243+
};
244+
219245
private computeStyles = (
220246
options: {
221247
interaction: Interaction;
@@ -229,6 +255,7 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
229255
const { interaction = this.state.interaction, aspectRatio } = options;
230256
const { layers, transitionDuration, lightColor, shadowColor } = this.props;
231257
const { width, height } = interaction === Interaction.Resize ? this.getDimensions(aspectRatio) : this.state;
258+
const staticStyles = this.getStaticStyles();
232259

233260
const bodyScrollTop =
234261
document.body.scrollTop ||
@@ -251,26 +278,14 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
251278
const lightAngle = Math.atan2(containerCenterY, containerCenterX) * 180 / Math.PI - 90;
252279

253280
const computedStyles: ILayeredImageStyles = {
254-
[Interaction.None]: {
255-
container: {
256-
transform: "none",
257-
transitionDuration: `${transitionDuration}s`,
258-
},
259-
layer: {
260-
transform: "none",
261-
},
262-
light: {
263-
backgroundImage: `linear-gradient(180deg, ${lightColor} 0%, transparent 80%)`,
264-
},
265-
shadow: {
266-
boxShadow: `0 8px 20px ${shadowColor}, 0 2px 4px ${shadowColor}`,
267-
transitionDuration: `${transitionDuration}s`,
268-
},
269-
},
281+
[Interaction.None]: { ...staticStyles },
282+
[Interaction.Resize]: { root: { height: `${height}px` } },
270283
[Interaction.Hover]: {
271284
container: {
272285
transform: `rotateX(${-clamp(containerRotationX, -8, 8)}deg)
273286
rotateY(${-clamp(containerRotationY, -8, 8)}deg)
287+
translateX(${-layerTranslationX * 6}px)
288+
translateY(${-layerTranslationY * 6}px)
274289
scale(1.1)`,
275290
},
276291
layer: (index: number) => ({
@@ -301,15 +316,10 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
301316
backgroundImage: `linear-gradient(${lightAngle}deg, ${lightColor} 0%, transparent 80%)`,
302317
},
303318
shadow: {
304-
boxShadow: `0 8px 20px ${shadowColor}, 0 2px 4px ${shadowColor}`,
319+
...staticStyles.shadow,
305320
transitionDuration: "0.075s",
306321
},
307322
},
308-
[Interaction.Resize]: {
309-
root: {
310-
height: `${height}px`,
311-
},
312-
},
313323
}[interaction];
314324

315325
if (preventDefault) {
@@ -339,49 +349,3 @@ export default class LayeredImage extends React.Component<ILayeredImageProps, IL
339349
});
340350
};
341351
}
342-
343-
const classes = {
344-
root: TypeStyle.style({
345-
position: "relative",
346-
width: "100%",
347-
transformStyle: "preserve-3d",
348-
cursor: "pointer",
349-
"-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)",
350-
}),
351-
container: TypeStyle.style({
352-
position: "absolute",
353-
width: "100%",
354-
height: "100%",
355-
transformStyle: "preserve-3d",
356-
}),
357-
layers: TypeStyle.style({
358-
position: "absolute",
359-
width: "100%",
360-
height: "100%",
361-
transformStyle: "preserve-3d",
362-
overflow: "hidden",
363-
background: "black",
364-
}),
365-
layer: TypeStyle.style({
366-
position: "absolute",
367-
width: "100%",
368-
height: "100%",
369-
backgroundRepeat: "no-repeat",
370-
backgroundPosition: "center",
371-
backgroundColor: "transparent",
372-
backgroundSize: "cover",
373-
opacity: 0,
374-
}),
375-
light: TypeStyle.style({
376-
position: "absolute",
377-
width: "100%",
378-
height: "100%",
379-
}),
380-
shadow: TypeStyle.style({
381-
position: "absolute",
382-
width: "100%",
383-
height: "100%",
384-
transform: "translateZ(-10px) scale(0.98)",
385-
transitionProperty: "transform, box-shadow",
386-
}),
387-
};

lib/classes.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { style } from "typestyle";
2+
3+
export default {
4+
root: style({
5+
position: "relative",
6+
width: "100%",
7+
transformStyle: "preserve-3d",
8+
cursor: "pointer",
9+
"-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)",
10+
}),
11+
container: style({
12+
position: "absolute",
13+
width: "100%",
14+
height: "100%",
15+
transitionProperty: "transform",
16+
transformStyle: "preserve-3d",
17+
}),
18+
layers: style({
19+
position: "absolute",
20+
width: "100%",
21+
height: "100%",
22+
background: "black",
23+
transformStyle: "preserve-3d",
24+
overflow: "hidden",
25+
}),
26+
layer: style({
27+
position: "absolute",
28+
width: "100%",
29+
height: "100%",
30+
backgroundRepeat: "no-repeat",
31+
backgroundPosition: "center",
32+
backgroundColor: "transparent",
33+
backgroundSize: "cover",
34+
transitionProperty: "transform, opacity",
35+
opacity: 0,
36+
}),
37+
light: style({
38+
position: "absolute",
39+
width: "100%",
40+
height: "100%",
41+
}),
42+
shadow: style({
43+
position: "absolute",
44+
width: "100%",
45+
height: "100%",
46+
transitionProperty: "transform, box-shadow",
47+
transform: "translateZ(-10px) scale(0.95)",
48+
}),
49+
};

0 commit comments

Comments
 (0)