Home Reference Source

src/effect.js

import tiltTransform from './transform'
import hitTest from './hit'

/**
 * tilt an element when it is pressed
 *
 * @param {Object} options
 * @param {HTMLElement} options.tiltElement - the element that will be transformed
 * @param {HTMLElement} options.hitElement - the element that listens to pointer events, by default it is the same as the tiltElement
 * @param {boolean} options.draggable
 * @param {number} options.tiltAmount
 * @param {number} options.sinkAmount
 *
 * @return {TiltEffectInstance} a tilt effect instance
 */
export default function TiltEffect({
  tiltElement,
  hitElement = tiltElement,
  draggable = false,
  tiltAmount = 1,
  sinkAmount = 1,
}) {
  let _isTilting = false

  const state = {
    get isTilting() { return _isTilting },
    set isTilting(val) {
      _isTilting = val
      tiltElement.classList.toggle('tilted', val)
      if (val) {
        hitElement.style.transform = 'none'
        state.bounds = hitElement.getBoundingClientRect()
      } else {
        tiltElement.style.transform = 'none'
      }
    },
    isPointerDown: false,
    bounds: null,
  }
  const onPointerMove = e => {
    const { clientX, clientY } = e

    if (hitTest(state.bounds, clientX, clientY)) {
      state.isTilting = true
    } else {
      state.isTilting = false
    }
    if (state.isTilting) {
      tiltElement.style.transform = tiltTransform(state.bounds, clientX, clientY, tiltAmount, sinkAmount)
    }
  }

  const onPointerUp = e => {
    state.isTilting = false
    state.isPointerDown = false
    window.removeEventListener('pointermove', onPointerMove)
    window.removeEventListener('pointerup', onPointerUp)
    window.removeEventListener('dragend', onPointerUp)
  }
  const onPointerDown = e => {
    state.isTilting = true
    state.isPointerDown = true

    const { clientX, clientY } = e
    setTimeout(() => {
      tiltElement.style.transform = tiltTransform(state.bounds, clientX, clientY, tiltAmount, sinkAmount)
    }, 0)

    window.addEventListener('pointermove', onPointerMove)
    window.addEventListener('pointerup', onPointerUp)
    window.addEventListener('dragend', onPointerUp)
  }

  hitElement.addEventListener('pointerdown', onPointerDown)

  return {
    destroy() {
      window.removeEventListener('pointermove', onPointerMove)
      window.removeEventListener('pointerup', onPointerUp)
      window.removeEventListener('dragend', onPointerUp)
      hitElement.removeEventListener('pointerdown', onPointerDown)
    },

    get tiltElement() { return tiltElement },
    set tiltElement(value) { tiltElement = value },

    get hitElement() { return hitElement },
    set hitElement(value) { hitElement = value },

    get draggable() { return draggable },
    set draggable(value) { drag = value },

    get tiltAmount() { return tiltAmount },
    set tiltAmount(value) { tiltAmount = value },

    get sinkAmount() { return sinkAmount },
    set sinkAmount(value) { sinkAmount = value },
  }
}

/**
 * @typedef {Object} TiltEffectInstance
 * @property {function()} destroy remove all event handlers created by this instance
 * @property {HTMLElement} tiltElement
 * @property {HTMLElement} hitElement
 * @property {boolean} draggable
 * @property {number} tiltAmount
 * @property {number} sinkAmount
 */