Skip to content

Instantly share code, notes, and snippets.

@jesperlandberg
Created March 15, 2019 12:13
Show Gist options
  • Select an option

  • Save jesperlandberg/382d4b7bfd0b20abd9910f03b53ec1d5 to your computer and use it in GitHub Desktop.

Select an option

Save jesperlandberg/382d4b7bfd0b20abd9910f03b53ec1d5 to your computer and use it in GitHub Desktop.
import config from '../config'
import bindAll from '../utils/bindAll'
import EventBus from '../utils/EventBus'
import { Events as GlobalRAFEvents } from '../utils/GlobalRAF'
import { Events as GlobalMouseEvents } from '../utils/GlobalMouse'
class Magnetic {
constructor() {
bindAll(this, ['run', 'mousePos', 'checkState'])
this.dom = {
elems: document.querySelectorAll('.js-magnetic')
}
this.state = {
mouse: {
x: 0,
y: 0
},
offset: 0
}
this.states = null
this.init()
}
getStates() {
this.states = []
this.dom.elems.forEach(el => {
const bounds = el.getBoundingClientRect()
const children = el.querySelectorAll('[data-magnetic-child-ratio]')
const state = {
el: el,
children: null,
bounds: bounds,
threshold: parseInt(el.dataset.magneticThreshold) || 100,
ratio: parseInt(el.dataset.magneticRatio) || 2,
max: parseInt(el.dataset.magneticMax) || 100,
isHovering: false,
current: {
x: 0,
y: 0,
},
transform: {
x: 0,
y: 0,
ease: parseInt(el.dataset.magneticEase) || 0.1
},
center: {
x: bounds.left + (bounds.width / 2),
y: bounds.top + (bounds.height / 2)
}
}
if (children) {
state.children = []
children.forEach(child => {
state.children.push({
el: child,
ratio: child.dataset.magneticChildRatio
})
})
}
this.states.push(state)
})
}
isVisible(bounds) {
const current = this.state.offset
const start = bounds.top - current
const end = bounds.bottom - current
const isVisible = start < config.height && end > 0
return isVisible
}
checkState(state) {
const { el, bounds, threshold, center, ratio } = state
const { mouse, offset } = this.state
const a = Math.abs(center.x - mouse.x)
const b = Math.abs(center.y - (mouse.y + offset))
const c = Math.sqrt(a * a + b * b)
const isMagnetic = c < (bounds.width / 2) + threshold
if (!state.isHovering && isMagnetic) {
el.classList.add('is-hover')
state.threshold = threshold * ratio
state.isHovering = true
} else if (state.isHovering && !isMagnetic) {
el.classList.remove('is-hover')
state.threshold = threshold / ratio
state.isHovering = false
}
return isMagnetic
}
run({ smooth }) {
this.state.offset = smooth
this.transformElems()
}
transformElems() {
this.states.forEach(state => {
const isVisible = this.isVisible(state.bounds)
if (!isVisible) return
const { current, transform, max, el } = state
const { mouse, offset } = this.state
const { width, height } = config
const isMagnetic = this.checkState(state)
current.x = isMagnetic ? (mouse.x - width / 2) / (width / max) : 0
current.y = isMagnetic ? (mouse.y - height / 2) / (height / max) : 0
transform.x += (current.x - transform.x) * transform.ease
transform.y += (current.y - transform.y) * transform.ease
el.style.transform = `
translate3d(${transform.x.toFixed(2)}px, ${transform.y.toFixed(2)}px, 0)
`
state.children && state.children.forEach(child => {
child.el.style.transform = `
translate3d(${(transform.x / child.ratio).toFixed(2)}px, ${(transform.y / child.ratio).toFixed(2)}px, 0)
`
})
})
}
mousePos({ x, y }) {
this.state.mouse.x = x
this.state.mouse.y = y
}
addListeners() {
EventBus.on(GlobalRAFEvents.TICK, this.run)
EventBus.on(GlobalMouseEvents.MOVE, this.mousePos)
}
init() {
this.addListeners()
this.getStates()
}
}
export default Magnetic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment