Making a cool sticky effect for a slider on React

There are many different libraries for implementing a slider with all possible effects. Some of the best for React are: ReactSlick and Swiper . But when a horizontal sticky effect was required for my project, nothing suitable was found.













In this article we will try to gradually create such a slider, maybe you will need it too!







Install the required packages



We will use the Create React App to create the project .







Create an application:







npx create-react-app my-app
      
      





We will not do the slider from scratch, but take the Swiper library, there are the most suitable events for which you will need to hook up (more on that later). Then we will need to install the following packages:







 npm i swiper react-id-swiper
      
      





And the last package (optional) to use the sass preprocessor:







 npm i node-sass
      
      





The result is package.json:







package.json









 { "name": "sticky-slider", "version": "0.1.0", "private": true, "dependencies": { "node-sass": "^4.13.0", "react": "^16.11.0", "react-dom": "^16.11.0", "react-id-swiper": "^2.3.2", "react-scripts": "3.2.0", "swiper": "^5.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
      
      





Great, now we are starting to implement the slider.







Create a simple slider



Let's start by creating a small file with our slides.







src/data.json









 [ { "title": "Slide 1", "color": "#aac3bf" }, { "title": "Slide 2", "color": "#c9b1bd" }, { "title": "Slide 3", "color": "#d5a29c" }, { "title": "Slide 4", "color": "#82a7a6" }, { "title": "Slide 5", "color": "#e6af7a" }, { "title": "Slide 6", "color": "#95be9e" }, { "title": "Slide 7", "color": "#97b5c5" } ]
      
      





After that we’ll make a regular slider with default effects.







 // src/components/StickySlider/StickySlider.jsx import React from 'react'; import Swiper from 'react-id-swiper'; import 'react-id-swiper/lib/styles/css/swiper.css'; import data from '../../data'; const StickySlider = () => { const params = { slidesPerView: 3, }; return ( <Swiper {...params}> {data.map((item, idx) => ( <div key={idx}> {item.title} </div> ))} </Swiper> ); }; export default StickySlider;
      
      





And accordingly, we create an index file for the component.







 // src/components/StickySlider/index.js export { default } from './StickySlider';
      
      





The only parameter that we described is slidesPerView



(the number of visible slides). We don’t need anything else, but all possible parameters of the swiper can be found here .







Create a separate component Slide, so that the appearance of the slider is ready.







 // src/components/Slide/Slide.jsx import React from 'react'; import css from './Slide.module.scss'; const Slide = ({ children, color }) => { return ( <div className={css.container}> <div className={css.content} style={{ background: color }} /> <footer className={css.footer}> {children} </footer> </div> ); }; export default Slide;
      
      





Styles for the slide.







 // src/components/Slide/Slide.module.scss .container { margin: 0 1em; border-radius: 4px; overflow: hidden; background-color: #fff; } .content { box-sizing: border-box; padding: 50% 0; } .footer { color: #333; font-weight: 700; font-size: 1.25em; text-align: center; padding: 1em; }
      
      





And accordingly the index file:







 // src/components/Slide/index.js export { default } from './Slide';
      
      





And update StickySlider a bit.







 // src/components/StickySlider/StickySlider.jsx import React from 'react'; import Swiper from 'react-id-swiper'; import 'react-id-swiper/lib/styles/css/swiper.css'; import Slide from '../Slide'; import data from '../../data'; const StickySlider = () => { const params = { slidesPerView: 3, }; return ( <Swiper {...params}> {data.map((item, idx) => ( <div key={idx}> {/*   */} <Slide color={item.color}> {item.title} </Slide> </div> ))} </Swiper> ); }; export default StickySlider;
      
      





Now insert this slider in App.jsx



, at the same time lay the minimum page structure.







 // App.jsx import React from 'react'; import StickySlider from './components/StickySlider'; import css from './App.module.scss'; const App = () => { return ( <div className={css.container}> <h1 className={css.title}>Sticky slider</h1> <div className={css.slider}> <StickySlider /> </div> </div> ); }; export default App;
      
      





And in the corresponding scss file we will write some styles.







 // App.module.scss .container { padding: 0 15px; } .title { font-weight: 700; font-size: 2.5em; text-align: center; margin: 1em 0; } .slider { margin: 0 -15px; }
      
      





So far, we have such a slider:













Cool, a start has been made, we will continue to do what we need from this slider.







Add a sticky effect



The swiper has two setTranslate



and setTransition



events that setTranslate



setTransition



.







Property When is triggered What returns
setTranslate



It works when we move the slider and at the moment when we lower it returns the value by which the slider is currently shifted, and in the case when we release it, the value to which it will be automatically brought
setTransition



it works when we start moving the slider, when we release it and when the slider is brought to the desired position returns the transition value in milliseconds


Add this to our StickySlider



component and immediately forward it to Slider



, there it will come in handy:







 // src/components/StickySlider/StickySlider.jsx import React, { useState, useEffect } from 'react'; import Swiper from 'react-id-swiper'; import 'react-id-swiper/lib/styles/css/swiper.css'; import Slide from '../Slide'; import data from '../../data'; const StickySlider = () => { const [swiper, updateSwiper] = useState(null); const [translate, updateTranslate] = useState(0); const [transition, updateTransition] = useState(0); const params = { slidesPerView: 3, }; useEffect(() => { if (swiper) { swiper.on('setTranslate', (t) => { updateTranslate(t); }); swiper.on('setTransition', (t) => { updateTransition(t); }); } }, [swiper]); return ( <Swiper getSwiper={updateSwiper} {...params}> {data.map((item, idx) => ( <div key={idx}> <Slide translate={translate} transition={transition} color={item.color} > {item.title} </Slide> </div> ))} </Swiper> ); }; export default StickySlider;
      
      





I advise you to move the slider and see in more detail what is displayed at this moment:







 // src/components/StickySlider/StickySlider.jsx // ... useEffect(() => { if (swiper) { swiper.on('setTranslate', (t) => { console.log(t, 'translate'); updateTranslate(t); }); swiper.on('setTransition', (t) => { console.log(t, 'transform'); updateTransition(t); }); } }, [swiper]); // ..
      
      





I use hooks to store state. If you are not familiar with them, I advise you to read the documentation (in Russian) .







Further, the most difficult will happen in the Slide



component.







We need indentation states from the left border of the slider and the width of the current slide:







 // src/components/StickySlider/StickySlider.jsx // ... const container = useRef(null); const [offsetLeft, updateOffsetLeft] = useState(0); const [width, updateWidth] = useState(1); // ...
      
      





They are added once at element initialization and are not changed. Therefore we use useEffect



with an empty array. At the same time, we get the parameters not of the slide itself, but its technical wrapper through parentElement



, since we will convert the current wrapper using the transform



property.







 // src/components/StickySlider/StickySlider.jsx // ... useEffect(() => { setTimeout(() => { const parent = container.current.parentElement; updateOffsetLeft(parent.offsetLeft); updateWidth(parent.offsetWidth); }, 0); }, []); // ...
      
      





The most important moment. We consider this whole thing and throw it in the styles:







 // src/components/Slide/Slide.jsx // ... const x = -translate - offsetLeft; const k = 1 - x / width; // [0 : 1] const style = x >= -1 ? { transform: `translateX(${x}px) scale(${k * 0.2 + 0.8})`, // [0.8 : 1] opacity: k < 0 ? 0 : k * 0.5 + 0.5, // [0.5 : 1] transition: `${transition}ms`, } : {}; // ...
      
      





The translate



property comes to us from the parent and it is the same for all slides. Therefore, to find an individual translate for one slide, subtract offsetLeft



from it.







The variable k



is a value from 0 to 1. Using this value, we will do the animation. This is a key variable, because it can be used to make any effects.







Now we compute the styles. The condition x >= -1



is fulfilled when the slide is in the animation zone, so when it is executed, we hang the styles on the side. The scale



and opacity



values ​​can be selected as you wish. The following intervals seemed to me most suitable: [0.8 : 1]



for scale



and [0.5 : 1]



for opacity



.







The transition



property is supplied directly from the library event.







Here's what happens after adding all of the above:







 // src/components/Slide/Slide.jsx import React, { useRef, useEffect, useState } from 'react'; import css from './Slide.module.scss'; const Slide = ({ children, translate, transition, color }) => { const container = useRef(null); const [offsetLeft, updateOffsetLeft] = useState(0); const [width, updateWidth] = useState(1); useEffect(() => { setTimeout(() => { const parent = container.current.parentElement; updateOffsetLeft(parent.offsetLeft); updateWidth(parent.offsetWidth); }, 0); }, []); const x = -translate - offsetLeft; const k = 1 - x / width; // [0 : 1] const style = x >= -1 ? { transform: `translateX(${x}px) scale(${k * 0.2 + 0.8})`, // [0.8 : 1] opacity: k < 0 ? 0 : k * 0.5 + 0.5, // [0.5 : 1] transition: `${transition}ms`, } : {}; return ( <div ref={container} style={style} className={css.container}> <div className={css.content} style={{ background: color }} /> <footer className={css.footer}> {children} </footer> </div> ); }; export default Slide;
      
      





Now add and add the following properties to the slide styles file:







 // src/components/Slide/Slide.module.scss .container { // ... transform-origin: 0 50%; //      transition-property: opacity, transform; // ,   } // ...
      
      





Well, that’s it, our effect is ready! You can see the finished example on my github .







Thanks for attention!








All Articles