CSS Slider Using `transform: translateX` To Cycle Through Images
Asked Answered
S

5

6

Is it possible to have a CSS slider cycle through two images when animating them with the translateX transform property?

I'm facing a couple of issues:

  1. I can't seem to get the second image to show even though it is in the HTML unless I use position: absolute and then the overflow: hidden doesn't work on the parent?

  2. How do I reset the first image to go back to the beginning to start it all again?

Note: in the animation shorthand, the animation lasts for 2.5s and there is an initial delay of 3s.

I only want to do with this with the translateX property because I want the 60FPS smoothness (it will be done with translate3d when completed, but to make the code easier to read I've used translateX). I don't wish to animate margin: left or the left property etc.

Any help would be amazing.

Code is below or link to Codepen: https://codepen.io/anna_paul/pen/ZEJrvRp

body {
  position: relative;
  margin: 0;
  display: flex;
  justify-content: center;
}

.container {
  width: 500px;
  height: 333px;
  overflow: hidden;
}

.slider-wrapper {
  display: flex;
}

.image {
  display: block;
}

.hero-image-1 {
  transform: translateX(0);
  animation: slide-out-image-1 2.5s 3s cubic-bezier(0.54, 0.12, 0.44, 1)
    forwards;
}

@keyframes slide-out-image-1 {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

.hero-image-2 {
  transform: translateX(100%);
  animation: slide-in-image-2 2.5s 3s cubic-bezier(0.54, 0.12, 0.44, 1) forwards;
}

@keyframes slide-in-image-2 {
  0% {
    transform: translateX(100%);
  }
  100% {
    transform: translateX(0);
  }
}
<div class="container">
  <div class="slider-wrapper">
    <picture>
      <img class="image hero-image-1" src="https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J">
    </picture>
    <picture>
      <img class="image hero-image-2" src="https://drive.google.com/uc?export=view&id=1iB9R1aoeYSmkPX9Ju3NNOZhKylOjCA0y">
    </picture>
  </div>
</div>
Softfinned answered 5/11, 2021 at 2:42 Comment(0)
B
5

To create a slider loop you need to define animation points, when slider moving and stopping. In you case it look that


2.5s + 3s +2.5s +3s = 11s or move + pause(out of container) + move + pause = totall.


For the first image we need to animate out of container, then move to start position (out of container), waiting (second image animate), then animate to initial positio and pause.

For the second image, we just show and then hide.

body {
  height: 100vh;
  background-color: hsl(201, 27%, 10%);
  color: white;
  display: grid;
  place-items: center;
  position: relative;
}

:root {
  --animate: 2.5s;
  --pause: 3s;
  --totall-duration: calc(var(--animate) * 2 + var(--pause) * 2);
}

.container {
  width: 500px;
  height: 333px;
  overflow: hidden;
}

.slider-wrapper {
  width: 200%;
  display: flex;
  position: relative;
}

.image {
  display: flex;
  width: 100%;
  height: 100%;
}

.hero-image-1 {
  top: 0;
  left: 0;
  position: absolute;
  animation: slide-out-image-1 var(--totall-duration) 3s
    cubic-bezier(0.54, 0.12, 0.44, 1) infinite;
}
/* start + pause + start + pause = totall */
/* 2.5s + 3s + 2.5s + 3s = 11s*/
@keyframes slide-out-image-1 {
  0% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-100%);
  }
  25.0001% {
    transform: translateX(100%);
  }
  50% {
    transform: translateX(100%);
  }
  75% {
    transform: translateX(0%);
  }
  100% {
    transform: translateX(0%);
  }
}

.hero-image-2 {
  top: 0;
  right: 0;
  position: absolute;
  animation: slide-in-image-2 var(--totall-duration) 3s
    cubic-bezier(0.54, 0.12, 0.44, 1) infinite;
}

/* start + pause + start + pause = totall */
/* 2.5s + 3s + 2.5s + 3s = 11s*/
@keyframes slide-in-image-2 {
  0% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-100%);
  }
  50% {
    transform: translateX(-100%);
  }
  75% {
    transform: translateX(-200%);
  }
  100% {
    transform: translateX(-200%);
  }
}
<div class="container">
  <div class="slider-wrapper">
    <picture class="hero-image-1">
      <img class="image" src="https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J" />
    </picture>
    <picture class="hero-image-2">
      <img class="image" src="https://drive.google.com/uc?export=view&id=1iB9R1aoeYSmkPX9Ju3NNOZhKylOjCA0y" />
    </picture>
  </div>
</div>
Bilbao answered 7/11, 2021 at 20:6 Comment(0)
L
1

We can do slider using background property.

<!DOCTYPE html>
<html>
  <head>
    <style>
      body { background-color: wheat; }
      h1 {
        text-align: center;
        color: tomato;
      }
      .slider {
        width: 60vw;
        margin: 0 auto;
        aspect-ratio: 485/323;

        transform: translate3d(0, 0, 0);
        background-image: url("https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J"),
          url("https://drive.google.com/uc?export=view&id=1iB9R1aoeYSmkPX9Ju3NNOZhKylOjCA0y");
        background-attachment: scroll, scroll;
        background-position: 0% 50%, 100% 50%;
        background-repeat: no-repeat, no-repeat;
        background-size: 100%, 100%;

        animation: slide-background 11s cubic-bezier(0.50, 0.0, 0.50, 1) 3s infinite;
      }
      @keyframes slide-background {
        0% { background-position: 0% 50%, 60vw 50%; }
        25% { background-position: -60vw 50%, 0% 50%; }
        25.01% { background-position: 60vw 50%, 0% 50%; }
        50% { background-position: 60vw 50%, 0% 50%; }
        75% { background-position: 0% 50%, -60vw 50%; }
        100% { background-position: 0% 50%, 60vw 50%; }
      }
    </style>
  </head>

  <body>
    <div class="slider"><h1>Cars!</h1></div>
  </body>
</html>

It resizes if you change browser size.

I know you want to use translate for performance. Don't know how much the transform: translate3d(0, 0, 0); trick works on animating background-* properties here. Does it get same hardware acceleration?

Loyola answered 11/11, 2021 at 9:32 Comment(1)
Yes it does. Also I can't use the background property I do need to use the <img/> element.Softfinned
C
0

No, without position:absolute its not possible.

For the Position Reset you can use Javascript. Here's a example;

var counter = 1;
setInterval(function()
{
  document.getElementById('radio' + counter).checked=true;
  counter++;
  if(counter>4){
    counter=1;
  }
}, 5000);
Contracture answered 6/11, 2021 at 19:57 Comment(0)
P
0

You could give the .container a position:relative to make overflow take effect. Absolutely positioned element require it's container to be a positioned element.

body {
  position: relative;
  margin: 0;
  display: flex;
  justify-content: center;
}

.container {
  width: 500px;
  height: 333px;
  overflow: hidden;
  position:relative;
}

.slider-wrapper {
  display: flex;
}

.image {
  position:absolute;
  display: block;
}

.hero-image-1 {
  transform: translateX(0);
  animation: slide-out-image-1 2.5s 3s cubic-bezier(0.54, 0.12, 0.44, 1)
    forwards;
}

@keyframes slide-out-image-1 {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

.hero-image-2 {
  transform: translateX(100%);
  animation: slide-in-image-2 2.5s 3s cubic-bezier(0.54, 0.12, 0.44, 1) forwards;
}

@keyframes slide-in-image-2 {
  0% {
    transform: translateX(100%);
  }
  100% {
    transform: translateX(0);
  }
}
<div class="container">
  <div class="slider-wrapper">
    <picture>
      <img class="image hero-image-1" src="https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J">
    </picture>
    <picture>
      <img class="image hero-image-2" src="https://drive.google.com/uc?export=view&id=1iB9R1aoeYSmkPX9Ju3NNOZhKylOjCA0y">
    </picture>
  </div>
</div>
Paniculate answered 7/11, 2021 at 4:24 Comment(0)
L
0

I have managed to create a scalable CSS only carousel. It is based on Christian Schaefer's idea of using scroll snap with shift.

Following is 5 slide carousel. In order to make it work you need to ensure:

  • You put copy of first .carousel-slide element at the end of the list.
  • set variables:
    • --no-slides: 6; //including duplicated first slide = 5+1
    • --slide-time: 4s; // stay time 75% + slide time 25%
  • modify the tostart keyframe rule to set times as per corresponding calculations.

     /* variables */
      :root {
        --no-slides: 6;  /* including duplicated first slide */
        --slide-time: 4s; /* stay time 75% + transition time 25% */
      }


      /* document styles */
      body {
        max-width: 400px;
        margin: 0 auto;
      }

      * {
        box-sizing: border-box;
        scrollbar-color: transparent transparent;
        scrollbar-width: 0px;
      }

      *::-webkit-scrollbar {
        width: 0;
      }

      *::-webkit-scrollbar-track {
        background: transparent;
      }

      *::-webkit-scrollbar-thumb {
        background: transparent;
        border: none;
      }

      * {
        -ms-overflow-style: none;
      }

      
      /* carousel styles */
      .carousel {
        position: relative;
        padding-top: 75%;
        perspective: 100px;
      }

      .carousel-viewport {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        display: flex;
        overflow-x: scroll;
        scroll-behavior: smooth;
        scroll-snap-type: x mandatory;
      }

      .carousel-viewport,
      .carousel-slide {
        list-style: none;
        margin: 0;
        padding: 0;
      }

      .carousel-slide {
        position: relative;
        flex: 0 0 100%;
        width: 100%;
        background: linear-gradient(90deg,#ff4e50, #f9d423);
      }

      

      .carousel-pusher {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        scroll-snap-align: center;
      }

      /* animations */
      @media (hover: hover) {
        .carousel-pusher {
          animation-delay: 0s;
          animation-name: tonext, snap;
          animation-timing-function: ease;
          animation-duration: var(--slide-time);
          animation-iteration-count: infinite;
        }

        .carousel-slide:last-child .carousel-pusher {
          animation-name: tostart, snap;
          animation-duration: calc(var(--slide-time) * (var(--no-slides) - 1)), var(--slide-time);
          animation-delay: var(--slide-time), 0s;
        }
      }

      @keyframes tonext {
        0% {
          filter: opacity(0);
        }
        75% {
          transform: translateX(0);
        }
        95% {
          transform: translateX(100%);
        }
        98% {
          transform: translateX(100%);
        }
        99% {
          filter: opacity(1);
          transform: translateX(0%);
        }
      }

      @keyframes snap {
        96% {
          scroll-snap-align: center;
        }
        97% {
          scroll-snap-align: none;
        }
        99% {
          scroll-snap-align: none;
        }
        100% {
          scroll-snap-align: center;
        }
      }

      @keyframes tostart {
        95.8% {     /* some fractions less than next frame time */
          transform: translateX(0%);
        }
        95.823% { /* 0.75 * (100%/--no-slides) + 100% - (100%/--no-slides) +-0.500 adjustment*/
      transform: translateX(calc((var(--no-slides)) * -100%));
    }
    99.5% {  /* 0.99 * (100%/--no-slides) + 100% - (100%/--no-slides)  +-0.500 adjustment*/
      transform: translateX(calc((var(--no-slides) - 2) * -100%));
    }
      }


      /* slide content styles */
      .content {
        padding: 20px ;
        font-size: 150px;
        text-align: center;
      }
<div class="carousel">
      <ul class="carousel-viewport">

        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">1 &#128522;</div>
        </li>

        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">2 &#128521;</div>
        </li>

        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">3 &#128516</div>
        </li>

        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">4 &#128580;</div>
        </li>


        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">5 &#128526;</div>
        </li>

        <!-- repeat first slide -->
        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content">1 &#128522;</div>
        </li>
      </ul>
    </div>

To get correct and smooth transitions tinker with tostart keyframe change times or add more frames.

Note: if animations stop you'll have to set carousel to first slide before starting animations again. There are three animations running in sync.


2 slide version

<!DOCTYPE html>
<html>
  <head>
    <style>
      /* variables */
      :root {
        --no-slides: 3; /* including duplicated first slide */
        --slide-time: 4s; /* stay time 75% + transition time 25% */
      }

      /* document styles */
      body {
        max-width: 400px;
        margin: 0 auto;
      }

      * {
        box-sizing: border-box;
        scrollbar-color: transparent transparent;
        scrollbar-width: 0px;
      }

      *::-webkit-scrollbar {
        width: 0;
      }

      *::-webkit-scrollbar-track {
        background: transparent;
      }

      *::-webkit-scrollbar-thumb {
        background: transparent;
        border: none;
      }

      * {
        -ms-overflow-style: none;
      }

      /* carousel styles */
      .carousel {
        position: relative;
        padding-top: 75%;
        perspective: 100px;
      }

      .carousel-viewport {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        display: flex;
        overflow-x: scroll;
        scroll-behavior: smooth;
        scroll-snap-type: x mandatory;
      }

      .carousel-viewport,
      .carousel-slide {
        list-style: none;
        margin: 0;
        padding: 0;
      }

      .carousel-slide {
        position: relative;
        flex: 0 0 100%;
        width: 100%;
        background-color: #f995;
      }

      .carousel-slide:nth-child(odd) {
        background-color: cadetblue;
      }
      .carousel-pusher {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        scroll-snap-align: center;
      }

      /* animations */
      @media (hover: hover) {
        .carousel-pusher {
          animation-delay: 0s;
          animation-name: tonext, snap;
          animation-timing-function: ease;
          animation-duration: var(--slide-time);
          animation-iteration-count: infinite;
        }

        .carousel-slide:last-child .carousel-pusher {
          animation-name: tostart, snap;
          animation-duration: calc(var(--slide-time) * (var(--no-slides) - 1)),
            var(--slide-time);
          animation-delay: var(--slide-time), 0s;
        }
      }

      @keyframes tonext {
        0% {
          filter: opacity(0);
        }
        75% {
          transform: translateX(0);
        }
        95% {
          transform: translateX(100%);
        }
        98% {
          transform: translateX(100%);
        }
        99% {
          filter: opacity(1);
          transform: translateX(0%);
        }
      }

      @keyframes snap {
        96% {
          scroll-snap-align: center;
        }
        97% {
          scroll-snap-align: none;
        }
        99% {
          scroll-snap-align: none;
        }
        100% {
          scroll-snap-align: center;
        }
      }

      @keyframes tostart {
        74.999% {
          /* some fractions less than next frame */
          transform: translateX(0%);
        }
        75% {
          /* 0.75 * (100%/--no-slides) + 100% - (100%/--no-slides) */
          transform: translateX(calc((var(--no-slides)) * -100%));
        }
        99.3% {
          /* 0.99 * (100%/--no-slides) + 100% - (100%/--no-slides)  -  <1.0 - 0.0 adjustment>*/
          transform: translateX(calc((var(--no-slides) - 2) * -100%));
        }
      }

      /* slide content styles */
      .content {
        object-fit: fill;
      }
      .image {
        padding: 0px;
        margin: 0px;
        width: 100%;
        height: 100%;
        object-fit: fill;
      }
    </style>
  </head>

  <body>
    <div class="carousel">
      <ul class="carousel-viewport">
        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content"
            ><img
              class="image"
              src="https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J"
          /></div>
        </li>

        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content"
            ><img
              class="image"
              src="https://drive.google.com/uc?export=view&id=1iB9R1aoeYSmkPX9Ju3NNOZhKylOjCA0y"
          /></div>
        </li>

        <!-- repeat first slide -->
        <li class="carousel-slide">
          <div class="carousel-pusher"></div>
          <div class="content"
            ><img
              class="image"
              src="https://drive.google.com/uc?export=view&id=1l7cTX35wqd-4eYvFL8A5QLZ7LbOF9m4J"
          /></div>
        </li>
      </ul>
    </div>
  </body>
</html>
Loyola answered 13/11, 2021 at 17:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.