Direction-aware hover effect

by avgustre

HTML

<header>
  <div class='container'>
    <h1>Direction-aware hover effect</h1>
    <p>CSS &amp; bits of JS</p>
  </div>
</header>
<div class='container'>
  <ul>
  </ul>
</div>

SCSS

@import url(https://fonts.googleapis.com/css?family=Bree+Serif);

$duration: 300ms;
$timing-fn: ease;
$turquoise: #1ABC9C;
$wet-asphalt: #34495E;
$midnight-blue: #2C3E50;
$clouds: #ECF0F1;

/* the important bits */
li {
  perspective: 400px;
}

.info {
  // hide-initial-state
  transform: rotate3d(1,0,0, 90deg);
  
  width: 100%;
  height: 100%;
  padding: 20px;
  position: absolute;
  top: 0;
  left: 0;
  border-radius: 4px;
  pointer-events: none;
  background-color: transparentize($turquoise, .1);
}

.in-top .info {
   transform-origin: 50% 0%;
   animation: in-top $duration $timing-fn 0ms 1 forwards;
}
.in-right .info {
  transform-origin: 100% 0%;
  animation: in-right $duration $timing-fn 0ms 1 forwards;
}
.in-bottom .info {
  transform-origin: 50% 100%;
  animation: in-bottom $duration $timing-fn 0ms 1 forwards;
}
.in-left .info {
  transform-origin: 0% 0%;
  animation: in-left $duration $timing-fn 0ms 1 forwards;
}

.out-top .info {
  transform-origin: 50% 0%;
  animation: out-top $duration $timing-fn 0ms 1 forwards;
}
.out-right .info {
  transform-origin: 100% 50%;
  animation: out-right $duration $timing-fn 0ms 1 forwards;
}
.out-bottom .info {
  transform-origin: 50% 100%;
  animation: out-bottom $duration $timing-fn 0ms 1 forwards;
}
.out-left .info {
  transform-origin: 0% 0%;
  animation: out-left $duration $timing-fn 0ms 1 forwards;
}

@keyframes in-top {
  from {transform: rotate3d(-1,0,0, 90deg)}
  to   {transform: rotate3d(0,0,0, 0deg)}}
@keyframes in-right {
  from {transform: rotate3d(0,-1,0, 90deg)}
  to   {transform: rotate3d(0,0,0, 0deg)}}
@keyframes in-bottom {
  from {transform: rotate3d(1,0,0, 90deg)}
  to   {transform: rotate3d(0,0,0, 0deg)}}
@keyframes in-left {
  from {transform: rotate3d(0,1,0, 90deg)}
  to   {transform: rotate3d(0,0,0, 0deg)}}

@keyframes out-top {
  from {transform: rotate3d(0,0,0, 0deg)}
  to   {transform: rotate3d(-1,0,0, 104deg)}}
@keyframes out-right {
  from {transform: rotate3d(0,0,0, 0deg)}
  to  ...

JavaScript

// - Noel Delgado | @pixelia_me
const nodes = [].slice.call(document.querySelectorAll('li'), 0);
const directions = {
  0: 'top',
  1: 'right',
  2: 'bottom',
  3: 'left'
};
const classNames = ['in', 'out'].map(p => Object.values(directions).map(d => `${p}-${d}`)).reduce((a, b) => a.concat(b));

const getDirectionKey = (ev, node) => {
  const {
    width,
    height,
    top,
    left
  } = node.getBoundingClientRect();
  const l = ev.pageX - (left + window.pageXOffset);
  const t = ev.pageY - (top + window.pageYOffset);
  const x = l - width / 2 * (width > height ? height / width : 1);
  const y = t - height / 2 * (height > width ? width / height : 1);
  return Math.round(Math.atan2(y, x) / 1.57079633 + 5) % 4;
};

class Item {
  constructor(element) {
    this.element = element;
    this.element.addEventListener('mouseover', ev => this.update(ev, 'in'));
    this.element.addEventListener('mouseout', ev => this.update(ev, 'out'));
  }

  update(ev, prefix) {
    this.element.classList.remove(...classNames);
    this.element.classList.add(`${prefix}-${directions[getDirectionKey(ev, this.element)]}`);
  }

}

nodes.forEach(node => new Item(node));