0
Follow
2
View

Recreating slider with gsap and react

diyuershi 注册会员
2023-01-26 03:38

So first off, in order to re-imagine any html snippet in terms of ReactJS, try and look for smilarities / repetation with in the snippet. Once you do that you would kind of get the gist of what can be seperated out as individual components and how it all ties together.

Now, just by looking at the html snippet, we can see that the slides are repeating, thus they can be kept as a seperate component. We also see that each slide displays a unique heading and paragraph text and also has a color scheme. So in order to make our Slide component dynamic, we can pass these as props to the component.

So our data that creates the whole Slider component (composed of Slide's) looks something like this:

const sliderData = [
    {
        id: '1',
        headerText: `I'm the first Box`,
        paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
            elit. Integer lacinia dui lectus. Donec scelerisque ipsum
            diam, ac mattis orci pellentesque eget. `,
        buttonText: 'Check Now',
        colors: {
            sliderBox: '#500033',
            sliderIllustration: '#FF0077',
            sliderInner: 'rgba(255, 0, 119, 0.4)',
            sliderButton: '#FF0077',
        },
    },
    ...
]

Next we notice, some of the UI was created using CSS, and this mainly caused the whole snippet to be static. So in order to achieve dynamicity we rendered some styles with in the component(s) based on the data we are getting. Like this:


Moreover, each slide was assigned a static class like box1, box2... etc. Therefore these styles need to be rendered for each slide like this:


(Notice that the object data in the above snippet corresponds to the object in our array sliderData that shapes up the whole slider component).

Once we have our UI ready, introducing GSAP animations was simple. First we created refs to the UI elements that were being used by GSAP in the animation process.

Next up, we triggered the animations just like how you would in plain javascript. The only difference here is that we executed them in the useEffect hook that runs once the functional component is loaded (empty dependancy array).

Moreover there were a few hard-coded stuff that animated just the five slides. Which we made dynamic by introducing let ratio = 100 / sliderData.length

The whole component:

import React, { useEffect, useRef } from 'react'
import './styleNew.css'
import { gsap } from 'gsap'

const sliderData = [
  {
    id: '1',
    headerText: `I'm the first Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#500033',
      sliderIllustration: '#FF0077',
      sliderInner: 'rgba(255, 0, 119, 0.4)',
      sliderButton: '#FF0077',
    },
  },
  {
    id: '2',
    headerText: `I'm the second Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#000050',
      sliderIllustration: '#0033FF',
      sliderInner: 'rgba(0, 51, 255, 0.4)',
      sliderButton: '#0033FF',
    },
  },
  {
    id: '3',
    headerText: `I'm the third Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#00501D',
      sliderIllustration: '#00FF44',
      sliderInner: 'rgba(0, 255, 68, 0.4)',
      sliderButton: '#00FF44',
    },
  },
  {
    id: '4',
    headerText: `I'm the fourth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#554D00',
      sliderIllustration: '#FF4E00',
      sliderInner: 'rgba(255, 78, 0, 0.4)',
      sliderButton: '#FF4E00',
    },
  },
  {
    id: '5',
    headerText: `I'm the fifth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#300050',
      sliderIllustration: '#8000FF',
      sliderInner: 'rgba(128, 0, 255, 0.4)',
      sliderButton: '#8000FF',
    },
  },
  {
    id: '6',
    headerText: `I'm the sixth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#000050',
      sliderIllustration: '#0033FF',
      sliderInner: 'rgba(0, 51, 255, 0.4)',
      sliderButton: '#0033FF',
    },
  },
  {
    id: '7',
    headerText: `I'm the seventh Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#00501D',
      sliderIllustration: '#00FF44',
      sliderInner: 'rgba(0, 255, 68, 0.4)',
      sliderButton: '#00FF44',
    },
  },
  {
    id: '8',
    headerText: `I'm the eighth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#554D00',
      sliderIllustration: '#FF4E00',
      sliderInner: 'rgba(255, 78, 0, 0.4)',
      sliderButton: '#FF4E00',
    },
  },
  {
    id: '9',
    headerText: `I'm the ninth Box`,
    paragraphText: `Lorem ipsum dolor sit amet, consectetur adipiscing 
        elit. Integer lacinia dui lectus. Donec scelerisque ipsum
        diam, ac mattis orci pellentesque eget. `,
    buttonText: 'Check Now',
    colors: {
      sliderBox: '#300050',
      sliderIllustration: '#8000FF',
      sliderInner: 'rgba(128, 0, 255, 0.4)',
      sliderButton: '#8000FF',
    },
  },
]

export default function SliderNew() {
  const slider = useRef(undefined)
  const prevButton = useRef(undefined)
  const nextButton = useRef(undefined)
  const trail = useRef([])

  useEffect(() => {
    console.log('sliders', slider)
    console.log('trail', trail)
    console.log('nextButton.current', nextButton.current)
    console.log('prevButton.current', prevButton.current)
    startGsapAnimations()
  }, [])

  const startGsapAnimations = () => {
    // Transform value
    let value = 0
    // trail index number
    let trailValue = 0
    // interval (Duration)
    let interval = 10000

    let ratio = 100 / sliderData.length

    const tl = gsap.timeline({
      defaults: { duration: 0.6, ease: 'power2.inOut' },
    })
    tl.from('.bg', { x: '-100%', opacity: 0 })
      .from('p', { opacity: 0 }, '-=0.3')
      .from('h1', { opacity: 0, y: '30px' }, '-=0.3')
      .from('button', { opacity: 0, y: '-40px' }, '-=0.8')

    // function to restart animation
    const animate = () => tl.restart()

    const slide = (condition) => {
      // CLear interval
      clearInterval(start)
      // update value and trailValue
      condition === 'increase' ? initiateINC() : initiateDEC()
      // move slide
      move(value, trailValue)
      // Restart Animation
      animate()
      // start interal for slides back
      start = setInterval(() => slide('increase'), interval)
    }

    // function for increase(forward, next) configuration
    const initiateINC = () => {
      // Remove active from all trails
      sliderData.forEach((_item, index) =>
        trail[index].classList.remove('active'),
      )
      // increase transform value
      //   console.log('initialInc~value', value)
      //   console.log('initialInc~calc', (sliderData.length - 1) * ratio)
      //   console.log(
      //     'initialInc~eq',
      //     Math.round(value) === Math.round((sliderData.length - 1) * ratio),
      //   )
      Math.round(value) === Math.round((sliderData.length - 1) * ratio)
        ? (value = 0)
        : (value += ratio)
      // update trailValue based on value
      trailUpdate()
    }

    // function for decrease(backward, previous) configuration
    const initiateDEC = () => {
      // Remove active from all trails
      sliderData.forEach((_item, index) =>
        trail[index].classList.remove('active'),
      )
      // decrease transform value
      Math.round(value) === 0
        ? (value = (sliderData.length - 1) * ratio)
        : (value -= ratio)
      // update trailValue based on value
      trailUpdate()
    }

    // function to transform slide
    const move = (S, T) => {
      // transform slider
      slider.current.style.transform = `translateX(-${S}%)`
      //add active class to the current trail
      console.log('trail', T)
      trail[Math.round(T)].classList.add('active')
    }

    const trailUpdate = () => {
      trailValue = value / ratio
      console.log('trailUpdate', trailValue)
    }

    // Start interval for slides
    let start = setInterval(() => slide('increase'), interval)

    nextButton.current.addEventListener('click', () => slide('increase'))
    prevButton.current.addEventListener('click', () => slide('decrease'))

    const clickCheck = (e) => {
      // CLear interval
      clearInterval(start)
      // Get selected trail
      const check = e.target

      // remove active class from all trails
      sliderData.forEach((_item, index) => {
        trail[index].classList.remove('active')
        if (check === trail[index]) {
          value = index * ratio
        }
      })

      // add active class
      check.classList.add('active')

      // update trail based on value
      trailUpdate()
      // transfrom slide
      move(value, trailValue)
      // start animation
      animate()
      // start interval
      start = setInterval(() => slide('increase'), interval)
    }

    // Add function to all trails
    sliderData.forEach((_item, index) =>
      trail[index].addEventListener('click', (ev) => clickCheck(ev)),
    )
  }

  return (
    <>
      
      
{sliderData.map((item, index) => ( ))}
{sliderData.map((item, index) => (
{ trail[index] = ref }} key={index} className={index == 0 ? `box${item.id} active` : `box${item.id}`} > {item.id}
))}
) } function Slide(props) { const { data } = props return ( <>

{data.headerText}

{data.paragraphText}

) } const Svg = React.forwardRef((props, ref) => { console.log('Svg', props) return ( ) })

The styleNew.css:

*,
*:before,
*:after {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

html {
  box-sizing: border-box;
  font-family: "Roboto", sans-serif;
  font-size: 62.5%;
}
@media only screen and (max-width: 800px) {
  html {
    font-size: 57%;
  }
}

body {
  background-color: #000;
  color: #fff;
  padding: 8rem;
}
@media only screen and (max-width: 1000px) {
  body {
    padding: 0;
  }
}

.container {
  position: relative;
  overflow: hidden;
  border-radius: 5rem;
}
@media only screen and (max-width: 1000px) {
  .container {
    border-radius: 0;
  }
}

@media only screen and (max-width: 1000px) {
  .slider {
    height: 100vh;
  }
}
.slider .box {
  height: 100%;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  align-items: center;
  overflow: hidden;
  position: relative;
}
@media only screen and (max-width: 650px) {
  .slider .box {
    grid-template-columns: 1fr;
    grid-template-rows: repeat(2, 1fr);
  }
}
.slider .box .bg {
  padding: 2rem;
  background-color: rgba(0, 0, 0, 0.2);
  width: 55%;
  transform: skewX(7deg);
  position: absolute;
  height: 100%;
  left: -10%;
  padding-left: 20rem;
  transform-origin: 0 100%;
}
@media only screen and (max-width: 800px) {
  .slider .box .bg {
    width: 65%;
  }
}
@media only screen and (max-width: 650px) {
  .slider .box .bg {
    width: 100%;
    left: 0;
    bottom: 0;
    height: 54%;
    transform: skewX(0deg);
  }
}
.slider .box .bg::before {
  content: "";
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  background-color: inherit;
  pointer-events: none;
  transform: skewX(10deg);
}
@media only screen and (max-width: 650px) {
  .slider .box .bg::before {
    width: 120%;
    bottom: 0;
    transform: skewX(0deg);
  }
}
.slider .box .details {
  padding: 5rem;
  padding-left: 10rem;
  z-index: 100;
  grid-column: 1/span 1;
  grid-row: 1/-1;
}
@media only screen and (max-width: 650px) {
  .slider .box .details {
    grid-row: 2/span 1;
    grid-column: 1/-1;
    text-align: center;
    padding: 2rem;
    transform: translateY(-9rem);
  }
}
.slider .box .details h1 {
  font-size: 3.5rem;
  font-weight: 500;
  margin-bottom: 0.5rem;
}
.slider .box .details p {
  display: inline-block;
  font-size: 1.3rem;
  color: #B5B4B4;
  margin-bottom: 2rem;
  margin-right: 5rem;
}
@media only screen and (max-width: 800px) {
  .slider .box .details p {
    margin-right: 0;
  }
}
.slider .box .details button {
  padding: 1rem 3rem;
  color: #fff;
  border-radius: 2rem;
  outline: none;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
}
.slider .box .details button:hover {
  opacity: 0.8;
}
.slider .box .details button:focus {
  outline: none;
  border: none;
}

.slider .illustration {
  grid-column: 2/-1;
  grid-row: 1/-1;
  justify-self: center;
}
@media only screen and (max-width: 650px) {
  .slider .illustration {
    grid-row: 1/span 1;
    grid-column: 1/-1;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}
.slider .illustration div {
  height: 25rem;
  width: 18rem;
  border-radius: 3rem;
  background-color: #FF0077;
  position: relative;
  transform: skewX(-10deg);
}
@media only screen and (max-width: 800px) {
  .slider .illustration div {
    height: 20rem;
    width: 15rem;
  }
}
.slider .illustration div::after, .slider .illustration div::before {
  content: "";
  position: absolute;
  height: 100%;
  width: 100%;
  border-radius: 3rem;
  top: 0;
  left: 0;
}
.slider .illustration div::after {
  transform: translate(4rem, -1rem);
}
.slider .illustration div::before {
  transform: translate(2rem, -2rem);
}

.prev,
.next,
.trail {
  z-index: 10000;
  position: absolute;
}

.prev,
.next {
  width: 4rem;
  cursor: pointer;
  opacity: 0.2;
  transition: all 0.3s ease;
}
@media only screen and (max-width: 650px) {
  .prev,
.next {
    display: none;
  }
}
.prev:hover,
.next:hover {
  opacity: 1;
}

.prev {
  top: 50%;
  left: 2%;
  transform: translateY(-50%);
}

.next {
  top: 50%;
  right: 2%;
  transform: translateY(-50%);
}


@media only screen and (max-width: 650px) {
  .trail {
    width: 90%;
    bottom: 13%;
  }
}
.trail div {
  padding: 2rem;
  border-top: 3px solid #fff;
  cursor: pointer;
  opacity: 0.3;
  transition: all 0.3s ease;
}
.trail div:hover {
  opacity: 0.6;
}
@media only screen and (max-width: 650px) {
  .trail div {
    padding: 1rem;
  }
}

.active {
  opacity: 1 !important;
}

About the Author

Question Info

Publish Time
2023-01-26 03:37
Update Time
2023-01-26 03:37

Related Question

Replace string bash script with sed

Send incoming http.request with a file to another server error NextPart: EOF

Typescript with React:在useEffect中使用自定义钩子

模拟assert_called_with将参数视为无序列表

Build Failed with 500和jupyter lab Build也不工作

vue用gsap操作暂停/重启为啥不行啊

Creating a video player with ffpyplayer and OpenCV

Problème with async函数

React App by CRA with TypeScript github pages got Error

jetpack Compose with Coil不加载URL图像