프론트엔드/컴포넌트

부드러운 가로 스크롤링 (3) - ease 함수 사용

syleemomo 2023. 7. 6. 08:01
728x90

https://spicyyoghurt.com/tools/easing-functions

 

Easing Functions for JavaScript - Animation Tool | Spicy Yoghurt

Get easing function for JavaScript and try them out on your own custom motions, using the animation tool. You can create animations in an interactive way and see the effects of using different easing functions. When satisfied, look-up the matching equation

spicyyoghurt.com

easing function 은 게임에서 캐릭터를 부드럽게 이동시킬때 자주 사용되는 개념이다. 물체를 화면에서 부드럽게 이동시켜야 하는 경우 적용할 수 있다. 여기서는 웹 화면에서 자주 사용되는 커로셀이나 슬라이드 화면을 구현하기 위하여 다양한 easing function 을 실험해볼수 있다. 

 

알고리즘의 핵심은 현재 화면에서 걸쳐있는 슬라이드가 다음 버튼을 클릭하면 맨 앞으로 이동한다. 이전 버튼을 클릭하면 화면에 걸쳐있던 슬라이드가 다시 화면에 걸치게 된다. 현재는 5번 슬라이드가 맨 앞이나 뒤로 간다.

슬라이드 이동화면 (1)

 

디바이스 크기가 줄어들면 현재 화면에 걸쳐있는 슬라이드도 변경된다. 현재는 4번 슬라이드가 화면에 걸쳐있으므로 이전/다음 버튼을 클릭하면 4번 슬라이드가 맨 앞이나 맨 뒤로 이동한다. 

슬라이드 이동화면 (2)

 

알고리즘을 구현하기 위하여 슬라이드를 감싸고 있는 전체 컨테이너 너비를 카드 너비로 나눈다. 그럼 화면에 걸쳐있는 슬라이드를 제외하고 컨테이너에서 완전히 보이는 슬라이드의 갯수를 계산할 수 있다. 이때 카드 사이에 마진이 존재하므로 카드 너비로 나눌때 마진도 포함해서 계산한다. 그런 다음 카드 너비만큼 다시 곱해주면 이전/다음 버튼 클릭시 얼마만큼 이동해야 하는지 거리를 구할수 있다.

예를 들어  현재 컨테이너 너비가 500px 이고 카드너비가 100px 이고 마진이 10px 이라고 가정하자! 500 / (100 + 10) = 4.5xxx 가 계산된다. 여기서 정수인 부분은 4이므로 4개의 슬라이드가 컨테이너 내부에서 가리지 않고 완전히 보인다. 그럼 4 * (100 + 10) = 440px 이 계산된다. 즉, 이전/다음 버튼 클릭시 해당 거리만큼 이동하게 된다. 디바이스 크기가 변하면 컨테이너 너비와 카드 너비를 동적으로 가져오므로 이전/다음 버튼 클릭시 이동할 거리는 새로 계산된다. 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <div class="actions">
      <span class="prev"><</span>
      <span class="next">></span>
    </div>
    <div class="carousel">
      <div class="card">1</div>
      <div class="card">2</div>
      <div class="card">3</div>
      <div class="card">4</div>
      <div class="card">5</div>
      <div class="card">6</div>
      <div class="card">7</div>
      <div class="card">8</div>
      <div class="card">9</div>
      <div class="card">10</div>
      <div class="card">11</div>
      <div class="card">12</div>
      <div class="card">13</div>
      <div class="card">14</div>
      <div class="card">15</div>
      <div class="card">16</div>
      <div class="card">17</div>
      <div class="card">18</div>
      <div class="card">19</div>
      <div class="card">20</div>
    </div>
  </div>
 
  <script src='app.js'></script>
</body>
</html>
.container{
  width: 50%; height: 300px;
  position: relative;
}
span{
  position: absolute;
  top: 50%; transform: translateY(-50%);
  color: white;
  font-size: 2rem;
  background-color: rgba(0, 0, 0, .4);
  width: 30px; height: 30px;
  line-height: 30px;
  border-radius: 50%;
  text-align: center;
  cursor: pointer;
  z-index: 1;
  user-select: none;
  font-size: 1rem;
  font-weight: bold;
}
span.prev{
  left: 1rem;
}
span.next{
  right: 1rem;
}
.carousel{
  width: 100%; height: 100%;
  display: flex;
  grid-gap: 10px;
  background-color: #fff;
  overflow-x: scroll;
}
.card{
  min-width: 200px; height: 100%;
  background-color: aquamarine;
  text-align: center;
  line-height: 300px;
  font-weight: bold; 
  font-size: 2rem;
  color: white;
  /* border: 1px solid red; */
}
const prevBtn = document.querySelector('.prev')
const nextBtn = document.querySelector('.next')
const carousel = document.querySelector('.carousel')
const container = document.querySelector('.container')
const card = carousel.querySelector('.card')
const cards = carousel.querySelectorAll('.card')
const margin = 10
let timerId = null 
let currentTime = 0
let startPoint = 0

function changePosition(unitTime, start, destination){
  currentTime += unitTime
  if(currentTime < 1){
    console.log('ongoing')
    // console.log(easeOutExpo (currentTime, start, destination, 1))
    carousel.scrollLeft = easeOutQuint(currentTime, start, destination-start, 1)
    // carousel.scrollLeft = destination
    prevBtn.removeEventListener('click', toPrevSlides)
    nextBtn.removeEventListener('click', toNextSlides)
    console.log(start, destination)
  }else{
    console.log('end')
    currentTime = 0
    clearInterval(timerId)
    prevBtn.addEventListener('click', toPrevSlides)
    nextBtn.addEventListener('click', toNextSlides)
    startPoint = destination
    console.log(startPoint)
  }
}
function toPrevSlides(){
  // console.log('prev')
  const distance = Math.floor(container.offsetWidth / (card.offsetWidth + margin)) * (card.offsetWidth + margin)
  let destination = startPoint-distance
  // console.log(distance, destination, startPoint)
  if(destination >= 0)
    timerId = setInterval(() => changePosition(0.01, startPoint, destination), 20)
  else
    destination = 1
}
function toNextSlides(){
  const distance = Math.floor(container.offsetWidth / (card.offsetWidth + margin)) * (card.offsetWidth + margin)
  let destination = startPoint+distance
  // console.log('distance: ', distance)

  // console.log(cards.length * (card.offsetWidth+margin))
  if(destination < cards.length * (card.offsetWidth+margin))
    timerId = setInterval(() => changePosition(0.01, startPoint, destination), 20)
  else
    destination = cards.length * (card.offsetWidth+margin)
}
prevBtn.addEventListener('click', toPrevSlides)
nextBtn.addEventListener('click', toNextSlides)

function easeOutExpo (t, b, c, d) {
  return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
}
function easeLinear (t, b, c, d) {
  return c * t / d + b;
}
// function easeInOutQuad (t, b, c, d) {
//   if ((t /= d / 2) < 1) return c / 2 * t * t + b;
//   return -c / 2 * ((--t) * (t - 2) - 1) + b;
// }
function easeInOutQuad(t, b, c, d) {
  t /= d/2;
  if (t < 1) return c/2*t*t + b;
  t--;
  return -c/2 * (t*(t-2) - 1) + b;
}
function easeInOutCubic (t, b, c, d) {
  if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
  return c / 2 * ((t -= 2) * t * t + 2) + b;
}
function easeInOutSine (t, b, c, d) {
  return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
}
function easeOutQuad (t, b, c, d) {
  return -c * (t /= d) * (t - 2) + b;
}
function easeOutCirc (t, b, c, d) {
  return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
}
function easeOutCubic (t, b, c, d) {
  return c * ((t = t / d - 1) * t * t + 1) + b;
}
function easeOutQuart (t, b, c, d) {
  return -c * ((t = t / d - 1) * t * t * t - 1) + b;
}
function easeOutQuint (t, b, c, d) {
  return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
}
728x90