Эластичный эффект для кнопки при наведении (CSS/JS)

Кнопка, при наведении на которую появляется эффект эластичности. Разберемся как можно реализовать такой эффект и использовать у себя на сайте.

Содержание
  1. HTML
  2. CSS (SCSS)
  3. JS

HTML

<a href="#" class="button">
	<canvas class="button__canvas"></canvas>
	<span class="button__text">Hover me I am liquid</span>
</a>

CSS (SCSS)

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background: #eef;
}

.button {
  position: relative;
  padding: 1.6em 2.8em;
  text-decoration: none;

  &__canvas {
    --offset: 32px;
    position: absolute;
    top: calc(var(--offset) * -1);
    left: calc(var(--offset) * -1);
    width: calc(100% + var(--offset) * 2);
    height: calc(100% + var(--offset) * 2);
    transition: opacity 2s ease;
  }

  &__text {
    position: relative;
    color: white;
    font-weight: bold;
    letter-spacing: .02em;
    pointer-events: none;
  }

  &:hover &__canvas {
    opacity: .85;
    transition: opacity .2s ease;
  }

  &:active &__canvas {
    opacity: 1;
    transition: none;
  }
}

JS

document.querySelectorAll('.button').forEach((elem) => {

	const canvas = elem.querySelector('.button__canvas')
	const ctx = canvas.getContext('2d')
	
	const offset = 32
	const background = '#eef'
	const foreground = '#7551e9'

	let points = []
	let position
	
	const distance = new Ola({
		value: offset
	})

	const size = () => {

		canvas.width = canvas.getBoundingClientRect().width
		canvas.height = canvas.getBoundingClientRect().height
		
		position = new Ola({
			x: canvas.width / 2,
			y: canvas.height / 2
		})
		
		const pixelsWidth = canvas.width - offset * 2
		const pixelsHeight = canvas.height - offset * 2
		
		const leftTop = [ offset, offset ]
		const rightTop = [ canvas.width - offset, offset ]
		const rightBottom = [ canvas.width - offset, canvas.height - offset ]
		const leftBottom = [ offset, canvas.height - offset ]
		
		points = []

		Array(pixelsWidth).fill().forEach((_, index) => {
			points.push([
				leftTop[0] + index,
				leftTop[1]
			])
		})
		
		Array(pixelsHeight).fill().forEach((_, index) => {
			points.push([
				rightTop[0],
				rightTop[1] + index
			])
		})
		
		Array(pixelsWidth).fill().forEach((_, index) => {
			points.push([
				rightBottom[0] - index,
				rightBottom[1]
			])
		})
		
		Array(pixelsHeight).fill().forEach((_, index) => {
			points.push([
				leftBottom[0],
				leftBottom[1] - index
			])
		})
		
	}

	size()
	
	const reset = () => {

		ctx.fillStyle = background
		ctx.fillRect(0, 0, canvas.width, canvas.height)

	}

	const draw = () => {

		ctx.fillStyle = foreground
		ctx.beginPath()
		
		points.forEach((point, index) => {
			
			const [ vx, vy ] = attract(point)
						
			if (index === 0) ctx.moveTo(vx, vy)
			else ctx.lineTo(vx, vy)
						
		})
		
		ctx.closePath()
		ctx.fill()

	}
	
	const attract = (point) => {
				
		const [ x, y ] = point

		const dx = x - position.x
		const dy = y - position.y
		
		const dist = Math.sqrt(dx * dx + dy * dy)
		const dist2 = Math.max(1, dist)
		
		const d = Math.min(dist2, Math.max(12, (dist2 / 4) - dist2))
		const D = dist2 * distance.value
		
		return [
			x + (d / D) * (position.x - x),
			y + (d / D) * (position.y - y)
		]

	}

	const loop = () => {
		reset()
		draw()
		requestAnimationFrame(loop)
	}

	window.onresize = size

	canvas.onmousemove = (e) => {

		position.set({
			x: e.clientX - e.target.getBoundingClientRect().left,
			y: e.clientY - e.target.getBoundingClientRect().top
		}, 200)
	
	}
	
	canvas.onmouseenter = () => {
		
		distance.set({
			value: 1
		}, 200)
		
	}
	
	canvas.onmouseleave = () => {
		
		position.set({
			x: canvas.width / 2,
			y: canvas.height / 2
		}, 2000)
		
		distance.set({
			value: offset
		}, 12000)
		
	}

	loop()

})

Источник
Оцените статью
( 1 оценка, среднее 5 из 5 )
Поделишься в соц. сетях? 🙂 Спасибо 🙏
Devtutor
Добавить комментарий