๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ“บ Front End/- React, Next.js

[React-Spring ์• ๋‹ˆ๋ฉ”์ด์…˜] ๋ฆฌ์•กํŠธ ์Šคํ”„๋ง์œผ๋กœ 3D ์นด๋“œ ๋งŒ๋“ค๊ธฐ

by Wonit 2020. 11. 4.

์˜ค๋Š˜ ํ•ด๋ณผ ๊ฒƒ์€ ์œ„์™€ ๊ฐ™์ด ์›€์ง์ด๋Š” ์นด๋“œ ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ ๊ฒƒ์ด๋‹ค.

Index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { useSpring, animated } from 'react-spring'
import './styles.css'

const calc = (x, y) => [-(y - window.innerHeight / 2) / 20, (x - window.innerWidth / 2) / 20, 1.1]
const trans = (x, y, s) => `perspective(600px) rotateX(${x}deg) rotateY(${y}deg) scale(${s})`

function Card() {
  const [props, set] = useSpring(() => ({ xys: [0, 0, 1], config: { mass: 5, tension: 350, friction: 40 } }))
  return (
    <animated.div
      class="card"
      onMouseMove={({ clientX: x, clientY: y }) => set({ xys: calc(x, y) })}
      onMouseLeave={() => set({ xys: [0, 0, 1] })}
      style={{ transform: props.xys.interpolate(trans) }}
    />
  )
}

ReactDOM.render(<Card />, document.getElementById('root'))

 

์—ฌ๊ธฐ์„œ calc ํ•จ์ˆ˜์™€ trans ํ•จ์ˆ˜๊ฐ€ ์“ฐ์˜€๋Š”๋ฐ, ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

ํ˜„์žฌ window์ฐฝ์˜ ๋†’์ด์™€ ๋„ˆ๋น„์— ์ค‘์•™ ๊ฐ’๊ณผ ์›€์ง์ž„์˜ ์˜์กด ๊ฐ’๋“ค์„ ์„ ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค์–ด์ฃผ๊ณ , transํ•จ์ˆ˜๋กœ css์˜ perspective ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•ด์ค€๋‹ค.

 

 

useSpring() ํ•จ์ˆ˜์—์„œ ์ดˆ๊ธฐ ์ด๋ฏธ์ง€์˜ xys ๊ฐ’์„ 0, 0, 1๋กœ ์ง€์ •ํ•ด์ค€๋‹ค.

๊ทธ๋ฆฌ๊ณ  config์—์„œ ๊ฐ์ข… ํ…์…˜ ๋ฐ ์ด๋ฏธ์ง€ ๊ฐ’๋“ค์„ ๋„ฃ์–ด์ฃผ๊ณ , animation์— div๋ฅผ ํ˜ธ์ถœํ•ด mouse ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ๊ฐ’์„ calc ํ•จ์ˆ˜์— ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„ฃ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  style ํƒœ๊ทธ๋ฅผ ์‚ฝ์ž…์‹œํ‚จ๋‹ค.

style.css

html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  background-color: white;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu,
    roboto, noto, segoe ui, arial, sans-serif;
  background: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  cursor: default;
}

#root {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: #f0f0f0;
}

.card {
  width: 45ch;
  height: 45ch;
  background: grey;
  border-radius: 5px;
  background-image: url(https://drscdn.500px.org/photo/435236/q%3D80_m%3D1500/v2?webp=true&sig=67031bdff6f582f3e027311e2074be452203ab637c0bd21d89128844becf8e40);
  background-size: cover;
  background-position: center center;
  box-shadow: 0px 10px 30px -5px rgba(0, 0, 0, 0.3);
  transition: box-shadow 0.5s;
  will-change: transform;
  border: 15px solid white;
}

.card:hover {
  box-shadow: 0px 30px 100px -10px rgba(0, 0, 0, 0.4);
}

๋Œ“๊ธ€