Skip to content

Instantly share code, notes, and snippets.

@ceane
Created November 27, 2015 23:51
Show Gist options
  • Save ceane/1d017af26f844ea0710b to your computer and use it in GitHub Desktop.
Save ceane/1d017af26f844ea0710b to your computer and use it in GitHub Desktop.
.apple-tv-parllax-root {
border-radius: 5px;
transform-style: preserve-3d;
transform-origin: center center;
perspective-origin: center center;
-webkit-tap-highlight-color: rgba(0,0,0,.4);
}
.apple-tv-parllax-root, .apple-tv-parllax-wrap, .apple-tv-parllax-layers, .apple-tv-parllax-shadow, .apple-tv-parllax-shine {
display: block;
perspective: none;
}
.apple-tv-parllax-wrap, .apple-tv-parllax-layers {
position: relative;
width: 100%;
height: 100%;
border-radius: 5px;
}
.apple-tv-parllax-wrap {
transition: all 0.2s ease-out;
}
.apple-tv-parllax-layers {
overflow: hidden;
transform-style: preserve-3d;
}
.apple-tv-parllax-shadow {
position: absolute;
top: 5%;
left: 5%;
width: 90%;
height: 90%;
transition: all 0.2s ease-out;
}
.apple-tv-parllax-over .apple-tv-parllax-shadow {
box-shadow: 0 45px 100px rgba(14,21,47,0.4), 0 16px 40px rgba(14,21,47,0.4);
}
.apple-tv-parllax-shine {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 5px;
background: linear-gradient(135deg, rgba(255,255,255,.25) 0%,rgba(255,255,255,0) 60%);
}
/* @flow */
/**
* Parent element with perspective attribute will not look great
*/
import React, { Component } from "react";
const cx = (...c) => c.filter(n => !!n).join(" ");
const styles = {
root: "apple-tv-parllax-root",
wrap: "apple-tv-parllax-wrap",
layers: "apple-tv-parllax-layers",
shadow: "apple-tv-parllax-shadow",
shine: "apple-tv-parllax-shine"
};
export default class AppleTVParallax extends Component {
constructor(props, context) {
super(props, context);
this.scrollTop = 0;
this.scrollLeft = 0;
this.state = {
userIsInteracting: false
};
}
_changeUserInteraction(userIsInteracting: boolean) {
return () => this.setState({ userIsInteracting });
}
_updateScrollPos() {
this.scrollTop = document.body.scrollTop;
this.scrollLeft = document.body.scrollLeft;
}
_unstyleTransforms() {
const { layers, shine, wrap } = this;
const total = React.Children.count(this.props.children);
wrap.style.transform = shine.style.background = shine.style.transform = "";
if (total > 1) {
for (var ly = 0, r = total; ly < total; ly++, r--) {
layers.children[ly].style.transform = "";
}
}
}
parallaxOnInteraction(e) {
const { layers, shine, wrap, scrollTop, scrollLeft } = this;
const { left, top, width, height } = wrap.getBoundingClientRect();
const total = React.Children.count(this.props.children);
let pageX = e.touches ? e.touches[0].pageX : e.pageX;
let pageY = e.touches ? e.touches[0].pageY : e.pageY;
let A = 320 / width;
let x = pageX - left - scrollLeft;
let y = pageY - top - scrollTop;
let pointerX = 0.52 - x / width;
let pointerY = 0.52 - y / height;
let dy = y - height / 2;
let dx = x - width / 2;
let yRotate = (pointerX - dx) * (0.07 * A);
let xRotate = (dy - pointerY) * (0.1 * A);
let angle = Math.max(0, Math.atan2(dy, dx) * 180 / Math.PI - 90);
requestAnimationFrame(() => {
wrap.style.transform = `rotateX(${xRotate}deg) rotateY(${yRotate}deg) scale3d(1.07,1.07,1.07)`;
shine.style.background = `linear-gradient(${angle}deg, rgba(255,255,255,${x / height * 0.4}) 0%, rgba(255,255,255,0) 80%)`;
shine.style.transform = `translateX(${(pointerX * total) - 0.1}px) translateY(${(pointerY * total) - 0.1}'px)`;
if (total > 1) {
for (var ly = 0, r = total; ly < total; ly++, r--) {
layers.children[ly].style.transform =
`translateX(${(pointerX * r) * ((ly * 2.5) / A)}px) translateY(${(pointerY * total) * ((ly * 2.5) / A)}px)`
}
}
});
}
componentWillUpdate(nextProps, nextState) {
if (!nextState.userIsInteracting) {
requestAnimationFrame(this._unstyleTransforms.bind(this));
}
}
componentDidMount() {
var { root } = this;
const { width } = root.getBoundingClientRect();
const perspective = Math.max(5 * width, 1200);
root.style.transform = `perspective(${perspective}px)`;
root.style.perspective = perspective + "px";
document.addEventListener("scroll", this._updateScrollPos.bind(this), false);
}
render() {
const { children, className } = this.props;
const { userIsInteracting } = this.state;
const interactionClassName = userIsInteracting ? styles.over : "";
const parallaxOnInteraction = this.parallaxOnInteraction.bind(this);
return (
<span className={cx(styles.root, className)} ref={e => this.root = e}>
<span
className={cx(styles.wrap, interactionClassName)}
onMouseEnter={this._changeUserInteraction(true)}
onMouseLeave={this._changeUserInteraction(false)}
onMouseMove={parallaxOnInteraction}
ref={e => this.wrap = e}>
<span className={styles.shadow} />
<span className={styles.layers} ref={e => this.layers = e}>
{children}
</span>
<span className={styles.shine} ref={e => this.shine = e} />
</span>
</span>
);
}
}
AppleTVParallax.propTypes = {
className: React.PropTypes.string
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment