Magnetic effect

Asked

Viewed 52 times

0

I’d like to use this effect on one of my projects.

const ui = {
	btn: document.querySelector('.c-magnetic-btn')
}

const state = {
	bounds: ui.btn.getBoundingClientRect(),
	threshold: parseInt(ui.btn.dataset.threshold),
	ratio: parseInt(ui.btn.dataset.ratio),
	isMagnetic: false,
	mouse: {
		x: 0,
		y: 0
	},
	ease: {
		x: 0,
		y: 0,
		scale: 1,
		value: ui.btn.dataset.ease
	},
	transform: {
		x: 0,
		y: 0,
		scale: 1,
		max: ui.btn.dataset.max
	},
	width: window.innerWidth,
	height: window.innerHeight,
	history: false,
	scale: ui.btn.dataset.scale
}

const mouseMove = ({ pageX, pageY }) => {
	Object.assign(state, {
		mouse: {
			x: pageX,
			y: pageY
		},
		isMagnetic: isMagnetic(pageX, pageY)
	})
}

const resize = () => {
	Object.assign(state, {
		bounds: ui.btn.getBoundingClientRect(),
		width: window.innerWidth,
		height: window.innerHeight
	})
}

const isMagnetic = (x, y) => {
	const { bounds } = state
	
	const centerX = bounds.left + (bounds.width / 2)
	const centerY = bounds.top + (bounds.height / 2)
	
	// use pythagorean theorem to calculate
	// cursor distance from center of btn
	// a^2 + b^2 = c^2
	const a = Math.abs(centerX - x)
	const b = Math.abs(centerY - y)
	const c = Math.sqrt(a * a + b * b)
	
	// true if cursor distance from center of btn is
	// equal to btn radius + threshold
	const isHover = c < (bounds.width / 2) + state.threshold

	if (!state.history && isHover) {
		ui.btn.classList.add('is-hover')
		Object.assign(state, {
			threshold: state.threshold * state.ratio,
			history: true
		})
	} else if (state.history && !isHover) {
		ui.btn.classList.remove('is-hover')
		Object.assign(state, {
			threshold: state.threshold / state.ratio,
			history: false
		})
	}

	return isHover
}

const run = () => {
	requestAnimationFrame(run)
	
	const { isMagnetic, transform, mouse, width, height, ease, max, scale } = state

	transform.x = isMagnetic ? (mouse.x - width / 2) / width * transform.max : 0
  transform.y = isMagnetic ? (mouse.y - height / 2) / height * transform.max : 0
	transform.scale = isMagnetic ? scale : 1
	
	// basic linear interpolation
	// https://www.youtube.com/watch?v=yWhgniVHROw
	ease.x += (transform.x - ease.x) * ease.value
	ease.y += (transform.y - ease.y) * ease.value
	ease.scale += (transform.scale - ease.scale) * ease.value

	Object.assign(ui.btn.style, {
		transform: `
			translate(
				${ease.x.toFixed(2)}px,
				${ease.y.toFixed(2)}px
			)
			translateZ(0)
			scale(
				${(ease.scale).toFixed(2)}
			)`
	})
}

const init = () => {
	document.addEventListener('mousemove', mouseMove)
	window.addEventListener('resize', resize)
	run()
}

init()
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
}

button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  text-align: center;
  text-decoration: none;
  outline: none;
  cursor: pointer;
  white-space: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none;
}

.u-fit {
  width: 100%;
  height: 100%;
}

.u-relative {
  position: relative;
}

.u-absolute {
  position: absolute;
}
.u-absolute.u-pos-tl {
  top: 0;
  left: 0;
}

.u-flex {
  display: flex;
}
.u-flex.u-center-xy {
  justify-content: center;
  align-items: center;
}

.c-magnetic-btn {
  width: 120px;
  height: 120px;
  border-radius: 50%;
}

.o-circle {
  display: inline-block;
  background-color: rgba(0, 0, 0, 0.015);
  border: 2px solid rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  z-index: -1;
  opacity: 0.2;
  transition: opacity 0.6s cubic-bezier(0.23, 1, 0.32, 1);
  will-change: opacity;
}

.t-btn-label {
  display: inline-block;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  font-family: 'Karla', sans-serif;
  font-weight: 700;
  letter-spacing: 1px;
  line-height: 1;
  color: rgba(0, 0, 0, 0.5);
  font-size: 13px;
}

.is-hover .o-circle {
  opacity: 0.4;
}
<button class="c-magnetic-btn u-relative" 
				data-threshold="60" 
				data-ratio="3.5"
				data-max="100"
				data-scale="1.1"
				data-ease="0.225">
	<span class="t-btn-label u-flex u-center-xy">Hover me.</span>
	<span class="o-circle u-fit u-absolute u-pos-tl"></span>
</button>

I don’t understand much of javascript, and the effect itself is all done with javascript, I would need help to use this effect on as many buttons/ elements as I want, I believe I have how to do this. I believe it is just some change in javascript, could help me in this?

1 answer

1


To apply this effect to any element and whenever you want, you have to place the code responsible for the effect within a function magnetic , which receives as a parameter the selector of the element concerned.

Example:

window.magnetic = function(selector) {

  const ui = {
    btn: document.querySelector('' + selector)
  }

  const state = {
    bounds: ui.btn.getBoundingClientRect(),
    threshold: parseInt(ui.btn.dataset.threshold),
    ratio: parseInt(ui.btn.dataset.ratio),
    isMagnetic: false,
    mouse: {
      x: 0,
      y: 0
    },
    ease: {
      x: 0,
      y: 0,
      scale: 1,
      value: ui.btn.dataset.ease
    },
    transform: {
      x: 0,
      y: 0,
      scale: 1,
      max: ui.btn.dataset.max
    },
    width: window.innerWidth,
    height: window.innerHeight,
    history: false,
    scale: ui.btn.dataset.scale
  }

  const mouseMove = ({
    pageX,
    pageY
  }) => {
    Object.assign(state, {
      mouse: {
        x: pageX,
        y: pageY
      },
      isMagnetic: isMagnetic(pageX, pageY)
    })
  }

  const resize = () => {
    Object.assign(state, {
      bounds: ui.btn.getBoundingClientRect(),
      width: window.innerWidth,
      height: window.innerHeight
    })
  }

  const isMagnetic = (x, y) => {
    const {
      bounds
    } = state

    const centerX = bounds.left + (bounds.width / 2)
    const centerY = bounds.top + (bounds.height / 2)

    // use pythagorean theorem to calculate
    // cursor distance from center of btn
    // a^2 + b^2 = c^2
    const a = Math.abs(centerX - x)
    const b = Math.abs(centerY - y)
    const c = Math.sqrt(a * a + b * b)

    // true if cursor distance from center of btn is
    // equal to btn radius + threshold
    const isHover = c < (bounds.width / 2) + state.threshold

    if (!state.history && isHover) {
      ui.btn.classList.add('is-hover')
      Object.assign(state, {
        threshold: state.threshold * state.ratio,
        history: true
      })
    } else if (state.history && !isHover) {
      ui.btn.classList.remove('is-hover')
      Object.assign(state, {
        threshold: state.threshold / state.ratio,
        history: false
      })
    }

    return isHover
  }

  const run = () => {
    requestAnimationFrame(run)

    const {
      isMagnetic,
      transform,
      mouse,
      width,
      height,
      ease,
      max,
      scale
    } = state

    transform.x = isMagnetic ? (mouse.x - width / 2) / width * transform.max : 0
    transform.y = isMagnetic ? (mouse.y - height / 2) / height * transform.max : 0
    transform.scale = isMagnetic ? scale : 1

    // basic linear interpolation
    // https://www.youtube.com/watch?v=yWhgniVHROw
    ease.x += (transform.x - ease.x) * ease.value
    ease.y += (transform.y - ease.y) * ease.value
    ease.scale += (transform.scale - ease.scale) * ease.value

    Object.assign(ui.btn.style, {
      transform: `
        translate(
          ${ease.x.toFixed(2)}px,
          ${ease.y.toFixed(2)}px
        )
        translateZ(0)
        scale(
          ${(ease.scale).toFixed(2)}
        )`
    })
  }

  const init = () => {
    document.addEventListener('mousemove', mouseMove)
    window.addEventListener('resize', resize)
    run()
  }

  init()


};

window.onload = () => {
/*
* Sempre quando quiser usar o efeito é 
* só chamar essa função e passar o selector do elemento que deseja aplicar esse efeito */
  magnetic('#c-magnetic-btn1');
  magnetic('#c-magnetic-btn2');
}
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
}

button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  text-align: center;
  text-decoration: none;
  outline: none;
  cursor: pointer;
  white-space: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none;
}

.u-fit {
  width: 100%;
  height: 100%;
}

.u-relative {
  position: relative;
}

.u-absolute {
  position: absolute;
}

.u-absolute.u-pos-tl {
  top: 0;
  left: 0;
}

.u-flex {
  display: flex;
}

.u-flex.u-center-xy {
  justify-content: center;
  align-items: center;
}

.c-magnetic-btn {
  width: 120px;
  height: 120px;
  border-radius: 50%;
}

.o-circle {
  display: inline-block;
  background-color: rgba(0, 0, 0, 0.015);
  border: 2px solid rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  z-index: -1;
  opacity: 0.2;
  transition: opacity 0.6s cubic-bezier(0.23, 1, 0.32, 1);
  will-change: opacity;
}

.t-btn-label {
  display: inline-block;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-family: 'Karla', sans-serif;
  font-weight: 700;
  letter-spacing: 1px;
  line-height: 1;
  color: rgba(0, 0, 0, 0.5);
  font-size: 13px;
}

.is-hover .o-circle {
  opacity: 0.4;
}
<button class="c-magnetic-btn u-relative" id="c-magnetic-btn1" data-threshold="60" data-ratio="3.5" data-max="100" data-scale="1.1" data-ease="0.225">
    <span class="t-btn-label u-flex u-center-xy">Hover me.</span>
    <span class="o-circle u-fit u-absolute u-pos-tl"></span>
  </button>

<div style="width: 500px;">
</div>

<button class="c-magnetic-btn u-relative" id="c-magnetic-btn2" data-threshold="60" data-ratio="3.5" data-max="100" data-scale="1.1" data-ease="0.225">
    <span class="t-btn-label u-flex u-center-xy">Hover me.</span>
    <span class="o-circle u-fit u-absolute u-pos-tl"></span>
  </button>

  • Thanks Cristiano! Solve my problem!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.