Skip to content

Instantly share code, notes, and snippets.

@Blazing-Mike
Last active September 29, 2024 16:58
Show Gist options
  • Save Blazing-Mike/2f939b57c05334a65a2ab040163c455d to your computer and use it in GitHub Desktop.
Save Blazing-Mike/2f939b57c05334a65a2ab040163c455d to your computer and use it in GitHub Desktop.
Solution multiple carousel
<div class="wrapper">
<div class="imageContainer">
<div class="example-img-container" style="--interval:7939.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:9939.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:6939.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:2939.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:4009.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:6939.155745263944" >
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
<div class="example-img-container" style="--interval:3939.155745263944">
<img src="https://a.storyblok.com/f/144881/7a42170da0/orchardside-school-41.jpg/m/500x0" alt="" loading="lazy">
<img src="https://a.storyblok.com/f/144881/2048x1365/2dc5855066/ws_neonflex_illuminated-signs_58.jpg/m/500x0" alt="" loading="lazy">
</div>
</div>
<div class="imageContainer middle">
<div class="image right"></div>
<div class="image right"></div>
<div class="image right"></div>
<div class="image right"></div>
<div class="image right"></div>
<div class="image right"></div>
<div class="image right"></div>
</div>
<div class="imageContainer">
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
<div class="image"></div>
</div>
</div>
<div class="hero-text">
<h1 class="hero-text">Hero text</h1>
</div>
let loop = horizontalLoop(".image", {speed: 1, repeat: -1, paddingRight: 0});
let exampleloop = horizontalLoop(".example-img-container", {speed: 0.5, repeat: -1, paddingRight: 0});
let rightloop = horizontalLoop(".right", {speed: 0.7, repeat: -1, paddingRight: 0});
// Set the animation to move to the left (timeScale = 1)
gsap.to(exampleloop, {timeScale: 1, duration: 0.3, overwrite: true});
gsap.to(loop, {timeScale: 1, duration: 0.3, overwrite: true});
// Set the animation to move to the right (timeScale = -1)
gsap.to(rightloop, {timeScale: -1, duration: 0.3, overwrite: true});
/*
This helper function makes a group of elements animate along the x-axis in a seamless, responsive loop.
Features:
- Uses xPercent so that even if the widths change (like if the window gets resized), it should still work in most cases.
- When each item animates to the left or right enough, it will loop back to the other side
- Optionally pass in a config object with values like "speed" (default: 1, which travels at roughly 100 pixels per second), paused (boolean), repeat, reversed, and paddingRight.
- The returned timeline will have the following methods added to it:
- next() - animates to the next element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
- previous() - animates to the previous element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
- toIndex() - pass in a zero-based index value of the element that it should animate to, and optionally pass in a vars object to control duration, easing, etc. Always goes in the shortest direction
- current() - returns the current index (if an animation is in-progress, it reflects the final index)
- times - an Array of the times on the timeline where each element hits the "starting" spot. There's also a label added accordingly, so "label1" is when the 2nd element reaches the start.
*/
function horizontalLoop(items, config) {
items = gsap.utils.toArray(items);
config = config || {};
let tl = gsap.timeline({repeat: config.repeat, paused: config.paused, defaults: {ease: "none"}, onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100)}),
length = items.length,
startX = items[0].offsetLeft,
times = [],
widths = [],
xPercents = [],
curIndex = 0,
pixelsPerSecond = (config.speed || 1) * 100,
snap = config.snap === false ? v => v : gsap.utils.snap(config.snap || 1),
totalWidth, curX, distanceToStart, distanceToLoop, item, i;
gsap.set(items, {
xPercent: (i, el) => {
let w = widths[i] = parseFloat(gsap.getProperty(el, "width", "px"));
xPercents[i] = snap(parseFloat(gsap.getProperty(el, "x", "px")) / w * 100 + gsap.getProperty(el, "xPercent"));
return xPercents[i];
}
});
gsap.set(items, {x: 0});
totalWidth = items[length-1].offsetLeft + xPercents[length-1] / 100 * widths[length-1] - startX + items[length-1].offsetWidth * gsap.getProperty(items[length-1], "scaleX") + (parseFloat(config.paddingRight) || 0);
for (i = 0; i < length; i++) {
item = items[i];
curX = xPercents[i] / 100 * widths[i];
distanceToStart = item.offsetLeft + curX - startX;
distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, "scaleX");
tl.to(item, {xPercent: snap((curX - distanceToLoop) / widths[i] * 100), duration: distanceToLoop / pixelsPerSecond}, 0)
.fromTo(item, {xPercent: snap((curX - distanceToLoop + totalWidth) / widths[i] * 100)}, {xPercent: xPercents[i], duration: (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond, immediateRender: false}, distanceToLoop / pixelsPerSecond)
.add("label" + i, distanceToStart / pixelsPerSecond);
times[i] = distanceToStart / pixelsPerSecond;
}
function toIndex(index, vars) {
vars = vars || {};
(Math.abs(index - curIndex) > length / 2) && (index += index > curIndex ? -length : length);
let newIndex = gsap.utils.wrap(0, length, index),
time = times[newIndex];
if (time > tl.time() !== index > curIndex) {
vars.modifiers = {time: gsap.utils.wrap(0, tl.duration())};
time += tl.duration() * (index > curIndex ? 1 : -1);
}
curIndex = newIndex;
vars.overwrite = true;
return tl.tweenTo(time, vars);
}
tl.next = vars => toIndex(curIndex+1, vars);
tl.previous = vars => toIndex(curIndex-1, vars);
tl.current = () => curIndex;
tl.toIndex = (index, vars) => toIndex(index, vars);
tl.times = times;
tl.progress(1, true).progress(0, true);
if (config.reversed) {
tl.vars.onReverseComplete();
tl.reverse();
}
return tl;
}
<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/gsap/dist/Observer.min.js"></script>
:root{
--interval : 6404.13357555363;
--rows: 3;
--gap: 0px;
--imageWidth: calc(150vh / var(--rows));
}
.wrapper{
overflow: hidden;
z-index: 1;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
font-size: 20px;
}
.hero-text{
z-index: 10;
position: relative;
color: white;
fill: white;
height: 10vh;
}
.imageContainer{
display: flex;
flex-direction: row;
width: 200%;
}
.middle{
border: 2px solid red;
}
.example-img-container{
flex: 0 0 auto;
position: relative;
display: flex;
flex-direction: column;
height: 275px;
max-width: 100%;
overflow: hidden;
}
.example-img-container img {
/* width: var(--imageWidth); */
min-height: 100%;
-o-object-fit: cover;
object-fit: cover;
-o-object-position: center;
object-position: center;
will-change: transform;
animation-delay: calc(var(--interval)* 1ms);
animation-duration: 12s;
animation-name:slideVertically;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
@keyframes slideVertically {
0% {
transform: translateY(0);
}
45% {
transform: translateY(0);
}
50% {
transform: translateY(-100%);
}
95% {
transform: translateY(-100%);
}
}
.imageContainer .image{
height: 275px;
width: 100%;
background-image: url('https://images.unsplash.com/photo-1538947151057-dfe933d688d1?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2370&q=80');
background-size: cover;
}
.image:nth-child(odd){
background-image: url('https://images.unsplash.com/photo-1489183988443-b37b7e119ba6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2372&q=80')
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment