<div class='center'>
  <div id='app'></div>
</div>
// For codepen display only:
html, body { width: 100%; height: 100%; }
.center {
  width: 100%; 
  height: 100%;
  display: flex;
  justify-content: center; 
  align-items: center;
}


// Parallax Hover Styles
.ph-wrapper {
  position: relative;
  transition: all 0.3s ease-out;
  margin: 0;

  .ph-shadow,
  .ph-layers,
  .ph-layer,
  .ph-lighting {
    transition: all 0.3s ease-out;
  }
  
  .ph-shadow,
  .ph-layers,
  .ph-layer,
  .ph-lighting {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
  }
  
  img {
    border-radius: 6px;
  }

  .ph-text > * {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    font-size: 4rem;
    color: #fff;
    font-weight: 300;
  }

  .ph-shadow {
    height: 90%;
    width: 90%;
    left: 5%;
    top: 5%;
    background: none;
  }

  .ph-lighting {
    background-image: linear-gradient(135deg,
                      rgba(255, 255, 255, 0) 0%,
                      rgba(255, 255, 255, 0) 33%);
  }

  .ph-heading {
    margin: 0;
    padding: 0;
    color: #fff;
    font-size: 3rem;
    text-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
  }

}
View Compiled
const config = {
  scale: 1.03, // How large to scale the item: 1.00 -> 1.10~
  rotation: 0.3, // Rotation modifier: 0.1 (more) -> 0.5 (less)
  alpha: 0.4, // Alpha channel modifer: 1.01 -> 1.1~
  shadow: 8 // How much the shadow moves
};

class ParallaxHover extends React.Component {static propTypes() {
    return {
      children: React.Proptypes.node.isRequired,
      width: React.Proptypes.string.isRequired,
      height: React.Proptypes.string.isRequired
    };
  }

  constructor(props) {
    super(props);
    this.state = {
      rotateX: 0,
      rotateY: 0,
      shadowMovement: 20,
      shadowSize: 50,
      scale: 1,
      angle: 0,
      alpha: 0
    };
  }

  __buildState(rotateX, rotateY, shadowMovement, shadowSize, scale, angle, alpha) {
    this.setState({
      rotateX: rotateX,
      rotateY: rotateY,
      shadowMovement: shadowMovement,
      shadowSize: shadowSize,
      scale: scale,
      angle: angle,
      alpha: alpha
    });
  }

  __buildTransformStrings(rotateX, rotateY, scale) {
    return {
      WebkitTransform: `perspective(1000px) scale(${scale}) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
      MozTransform: `perspective(1000px) scale(${scale}) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`,
      transform: `perspective(1000px) scale(${scale}) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
    };
  }

  __calculateDistance(bounds, offsetX, offsetY) {
    const distanceX = Math.pow(offsetX - (bounds.width / 2), 2);
    const distanceY = Math.pow(offsetY - (bounds.height / 2), 2);
    return Math.floor(Math.sqrt(distanceX + distanceY));
  }

  __calculateAlphaFromCenter(current) {
    const max = Math.max(this.props.width, this.props.height);
    return current / max * config.alpha;
  }

  __handleMouseMove({ pageX, pageY, nativeEvent}) {
    const width = this.props.width;
    const height = this.props.height;
    const { scrollTop: scrollTop, scrollLeft: scrollLeft } = document.body;

    const bounds = this.refs.wrapper.getBoundingClientRect();
    const centerX = this.props.width / 2;
    const centerY = this.props.height / 2;
    const widthMultiplier = 320 / this.props.width;

    const offsetX = 0.52 - (pageX - bounds.left - scrollLeft) / width;
    const offsetY = 0.52 - (pageY - bounds.top - scrollTop) / height;

    const deltaX = (pageX - bounds.left - scrollLeft) - centerX;
    const deltaY = (pageY - bounds.top - scrollTop) - centerY;
    const rotateX = (deltaY - offsetY) * (0.08 * widthMultiplier);
    const rotateY = (offsetX - deltaX) * (0.04 * widthMultiplier);
    const angleRad = Math.atan2(deltaY, deltaX);
    const angleRaw = angleRad * 180 / Math.PI - 90;
    const angleDeg = angleRaw < 0 ? angleRaw + 360 : angleRaw;
    const distanceFromCenter = this.__calculateDistance(bounds, nativeEvent.offsetX, nativeEvent.offsetY);
    const shadowMovement = centerY * 0.25;
    const shadowSize = 120;
    const alpha = this.__calculateAlphaFromCenter(distanceFromCenter);

    this.__buildState(rotateX, rotateY, shadowMovement, shadowSize, config.scale, angleDeg, alpha);
  }

  __handleMouseLeave() {
    this.__buildState(0, 0, 20, 50, 1, 0, 0);
  }

  __renderChildren(children) {
    const st = this.state;

    if (!Array.isArray(children)) {
      const styles = this.__buildTransformStrings(st.rotateX, st.rotateY, st.scale);
      console.log(this.__buildTransformStrings(st.rotateX, st.rotateY, st.scale));
      return <div style={styles} className='ph-layer'>{children}</div>;
    }

    return children.map((layer, key) => {
      const num = key + 1;
      const rotateX = Math.floor(st.rotateX / num);
      const rotateY = Math.floor(st.rotateY / num);
      let styles = this.__buildTransformStrings(rotateX, rotateY, st.scale);
      let textClass;

      if (layer.ref === 'text') {
        textClass = 'ph-text';
        const shadow = {
          textShadow: `${rotateY * 0.5}px ${rotateX * 0.5}px 10px rgba(0, 0, 0, 0.5)`
        };

        styles = Object.assign({}, shadow, styles);
      }

      return <div style={styles} className={`ph-layer ${textClass}`} key={key}>{layer}</div>;
    });
  }

  render() {
    const st = this.state;
    const baseTransforms = this.__buildTransformStrings(st.rotateX, st.rotateY, st.scale);

    const stylesWrapper = Object.assign({}, baseTransforms, {
      width: this.props.width,
      height: this.props.height
    });

    const stylesShadow = Object.assign({}, baseTransforms, {
      boxShadow: `0px ${st.shadowMovement}px ${st.shadowSize}px rgba(0, 0, 0, 0.6)`
    });

    const stylesLighting = Object.assign({}, baseTransforms, {
      backgroundImage: `linear-gradient(${st.angle}deg, rgba(255,255,255, ${st.alpha}) 0%, rgba(255,255,255,0) 40%)`
    });

    return (
      <div style={{ transformStyle: 'preserve-3d'}}>
        <figure ref='wrapper' className='ph-wrapper' style={stylesWrapper} onMouseMove={this.__handleMouseMove.bind(this)} onMouseLeave={this.__handleMouseLeave.bind(this)}>
          <div className='ph-shadow' style={stylesShadow} />
          <div className='ph-layers'>
            {this.__renderChildren(this.props.children)}
          </div>
          <div className='ph-lighting' style={stylesLighting} />
        </figure>
      </div>
    );
  }
}

const App = React.createClass({
  render() {
    return (
      <div>
        <ParallaxHover width='500' height='300'>
          <img ref='image' src='http://lorempixel.com/500/300/abstract/' />
          <h1 ref='text'>Parallax Hover</h1>
        </ParallaxHover>
      </div>
    );
  }
});

const Node = document.getElementById('app');
ReactDOM.render(React.createElement(App), Node);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react.js
  2. //cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react-dom.js