Dynamic Backgrounds with a 4-Tile Image Slider

Follow on LinkedIn

In this tutorial, we’ll walk through the process of building a stylish and responsive image slider using HTML, CSS, and JavaScript. This slider will showcase beautiful destinations from around the world, with smooth transitions and interactive elements. Let’s dive into the code and understand how each part contributes to the final product.

1. Setting Up the HTML Structure

The HTML file serves as the backbone of our slider. Here, we define the structure and content of the slider, including the navigation bar, slide details, and interactive elements like buttons.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Globe Express Slider</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="indicator"></div>
    <nav>
        <div>
            <div class="svg-container">
                <!-- Globe Icon -->
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M12 21..."/>
                </svg>
            </div>
            <div>Globe Express</div>
        </div>
        <div>
            <div class="active">Home</div>
            <!-- Navigation Links -->
            <div>Holidays</div>
            <div>Destinations</div>
            <div>Flights</div>
            <div>Offers</div>
            <div>Contact</div>
            <div class="svg-container">
                <!-- Search Icon -->
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M21 21..."/>
                </svg>
            </div>
            <div class="svg-container">
                <!-- Profile Icon -->
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                    <path fill-rule="evenodd" d="M7.5 6..."/>
                </svg>
            </div>
        </div>
    </nav>

    <div id="demo"></div>

    <div class="details" id="details-even">
        <!-- Slide Content -->
        <div class="place-box">
            <div class="text">Switzerland Alps</div>
        </div>
        <div class="title-box-1"><div class="title-1">SAINT</div></div>
        <div class="title-box-2"><div class="title-2">ANTONIEN</div></div>
        <div class="desc">
            Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat...
        </div>
        <div class="cta">
            <button class="bookmark">
                <!-- Bookmark Icon -->
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                    <path fill-rule="evenodd" d="M6.32 2.577..."/>
                </svg>
            </button>
            <button class="discover">Discover Location</button>
        </div>
    </div>

    <div class="pagination" id="pagination">
        <!-- Pagination Arrows -->
        <div class="arrow arrow-left">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5..."/>
            </svg>
        </div>
        <div class="arrow arrow-right">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5..."/>
            </svg>
        </div>
        <div class="progress-sub-container">
            <div class="progress-sub-background">
                <div class="progress-sub-foreground"></div>
            </div>
        </div>
        <div class="slide-numbers" id="slide-numbers"></div>
    </div>

    <div class="cover"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>   
    <script src="index.js"></script>
</body>
</html>
Key Elements:
  • Navigation Bar: The <nav> tag houses our navigation menu, featuring links to different sections of the site, an active indicator, and SVG icons.
  • Slide Details: Each slide detail is encapsulated in a <div> with the class details. This includes the title, description, and call-to-action buttons.
  • Pagination: The pagination section contains the arrows for navigating through the slides and a progress bar that visually represents the slide transitions.

2. Designing with CSS

The CSS file is crucial for styling our slider, making it visually appealing and ensuring it’s responsive across different devices.





body {
	 margin: 0;
	 background-color: #1a1a1a;
	 color: #fff DD;
	 position: relative;
	 overflow: hidden;
	 font-family: "Inter", sans-serif;
}
 .card {
	 position: absolute;
	 left: 0;
	 top: 0;
	 background-position: center;
	 background-size: cover;
	 box-shadow: 6px 6px 10px 2px rgba(0, 0, 0, 0.6);
}
 #btn {
	 position: absolute;
	 top: 690px;
	 left: 16px;
	 z-index: 99;
}
 .card-content {
	 position: absolute;
	 left: 0;
	 top: 0;
	 color: #fff DD;
	 padding-left: 16px;
}
 .content-place {
	 margin-top: 6px;
	 font-size: 13px;
	 font-weight: 500;
}
 .content-place {
	 font-weight: 500;
}
 .content-title-1, .content-title-2 {
	 font-weight: 600;
	 font-size: 20px;
	 font-family: "Oswald", sans-serif;
}
 .content-start {
	 width: 30px;
	 height: 5px;
	 border-radius: 99px;
	 background-color: #fff DD;
}
 .details {
	 z-index: 22;
	 position: absolute;
	 top: 240px;
	 left: 60px;
}
 .details .place-box {
	 height: 46px;
	 overflow: hidden;
}
 .details .place-box .text {
	 padding-top: 16px;
	 font-size: 20px;
}
 .details .place-box .text:before {
	 top: 0;
	 left: 0;
	 position: absolute;
	 content: "";
	 width: 30px;
	 height: 4px;
	 border-radius: 99px;
	 background-color: white;
}
 .details .title-1, .details .title-2 {
	 font-weight: 600;
	 font-size: 72px;
	 font-family: "Oswald", sans-serif;
}
 .details .title-box-1, .details .title-box-2 {
	 margin-top: 2px;
	 height: 100px;
	 overflow: hidden;
}
 .details > .desc {
	 margin-top: 16px;
	 width: 500px;
}
 .details > .cta {
	 width: 500px;
	 margin-top: 24px;
	 display: flex;
	 align-items: center;
}
 .details > .cta > .bookmark {
	 border: none;
	 background-color: #ecad29;
	 width: 36px;
	 height: 36px;
	 border-radius: 99px;
	 color: white;
	 display: grid;
	 place-items: center;
}
 .details > .cta > .bookmark svg {
	 width: 20px;
	 height: 20px;
}
 .details > .cta > .discover {
	 border: 1px solid #fff;
	 background-color: transparent;
	 height: 36px;
	 border-radius: 99px;
	 color: #fff;
	 padding: 4px 24px;
	 font-size: 12px;
	 margin-left: 16px;
	 text-transform: uppercase;
}
 nav {
	 position: fixed;
	 left: 0;
	 top: 0;
	 right: 0;
	 z-index: 50;
	 display: flex;
	 align-items: center;
	 justify-content: space-between;
	 padding: 20px 36px;
	 font-weight: 500;
}
 nav svg {
	 width: 20px;
	 height: 20px;
}
 nav .svg-container {
	 width: 20px;
	 height: 20px;
}
 nav > div {
	 display: inline-flex;
	 align-items: center;
	 text-transform: uppercase;
	 font-size: 14px;
}
 nav > div:first-child {
	 gap: 10px;
}
 nav > div:last-child {
	 gap: 24px;
}
 nav > div:last-child > .active {
	 position: relative;
}
 nav > div:last-child > .active:after {
	 bottom: -8px;
	 left: 0;
	 right: 0;
	 position: absolute;
	 content: "";
	 height: 3px;
	 border-radius: 99px;
	 background-color: #ecad29;
}
 .indicator {
	 position: fixed;
	 left: 0;
	 right: 0;
	 top: 0;
	 height: 5px;
	 z-index: 60;
	 background-color: #ecad29;
}
 .pagination {
	 position: absolute;
	 left: 0px;
	 top: 0px;
	 display: inline-flex;
}
 .pagination > .arrow {
	 z-index: 60;
	 width: 50px;
	 height: 50px;
	 border-radius: 999px;
	 border: 2px solid #fff 55;
	 display: grid;
	 place-items: center;
}
 .pagination > .arrow:nth-child(2) {
	 margin-left: 20px;
}
 .pagination > .arrow svg {
	 width: 24px;
	 height: 24px;
	 stroke-width: 2;
	 color: #fff 99;
}
 .pagination .progress-sub-container {
	 margin-left: 24px;
	 z-index: 60;
	 width: 500px;
	 height: 50px;
	 display: flex;
	 align-items: center;
}
 .pagination .progress-sub-container .progress-sub-background {
	 width: 500px;
	 height: 3px;
	 background-color: #fff 33;
}
 .pagination .progress-sub-container .progress-sub-background .progress-sub-foreground {
	 height: 3px;
	 background-color: #ecad29;
}
 .pagination .slide-numbers {
	 width: 50px;
	 height: 50px;
	 overflow: hidden;
	 z-index: 60;
	 position: relative;
}
 .pagination .slide-numbers .item {
	 width: 50px;
	 height: 50px;
	 position: absolute;
	 color: white;
	 top: 0;
	 left: 0;
	 display: grid;
	 place-items: center;
	 font-size: 32px;
	 font-weight: bold;
}
 .cover {
	 position: absolute;
	 left: 0;
	 top: 0;
	 width: 100vw;
	 height: 100vh;
	 background-color: #fff;
	 z-index: 100;
}
 
Key Styling:
  • Global Styles: The body tag is styled with a dark background and white text for a clean, modern look.
  • Cards: The .card class positions the slides absolutely, allowing for easy transitions and animations.
  • Navigation: The nav section is styled for fixed positioning, making it accessible as the user navigates through the slides.
  • Pagination: The .pagination class handles the styling of navigation arrows and progress bars, creating an intuitive user interface.

3. Adding Interactivity with JavaScript

Finally, the JavaScript file drives the functionality of the slider, allowing users to transition between slides and interact with the content.

const data = [
    {
        place:'Switzerland Alps',
        title:'SAINT',
        title2:'ANTONIEN',
        description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. It\'s a hidden gem for backcountry skiing in winter and boasts lush trails for hiking and mountain biking during the warmer months.',
        image:'https://assets.codepen.io/3685267/timed-cards-1.jpg'
    },
    {
        place:'Japan Alps',
        title:'NANGANO',
        title2:'PREFECTURE',
        description:'Nagano Prefecture, set within the majestic Japan Alps, is a cultural treasure trove with its historic shrines and temples, particularly the famous Zenkō-ji. The region is also a hotspot for skiing and snowboarding, offering some of the country\'s best powder.',
        image:'https://assets.codepen.io/3685267/timed-cards-2.jpg'
    },
    {
        place:'Sahara Desert - Morocco',
        title:'MARRAKECH',
        title2:'MEROUGA',
        description:'The journey from the vibrant souks and palaces of Marrakech to the tranquil, starlit sands of Merzouga showcases the diverse splendor of Morocco. Camel treks and desert camps offer an unforgettable immersion into the nomadic way of life.',
        image:'https://assets.codepen.io/3685267/timed-cards-3.jpg'
    },
    {
        place:'Sierra Nevada - USA',
        title:'YOSEMITE',
        title2:'NATIONAL PARAK',
        description:'Yosemite National Park is a showcase of the American wilderness, revered for its towering granite monoliths, ancient giant sequoias, and thundering waterfalls. The park offers year-round recreational activities, from rock climbing to serene valley walks.',
        image:'https://assets.codepen.io/3685267/timed-cards-4.jpg'
    },
    {
        place:'Tarifa - Spain',
        title:'LOS LANCES',
        title2:'BEACH',
        description:'Los Lances Beach in Tarifa is a coastal paradise known for its consistent winds, making it a world-renowned spot for kitesurfing and windsurfing. The beach\'s long, sandy shores provide ample space for relaxation and sunbathing, with a vibrant atmosphere of beach bars and cafes.',
        image:'https://assets.codepen.io/3685267/timed-cards-5.jpg'
    },
    {
        place:'Cappadocia - Turkey',
        title:'Göreme',
        title2:'Valley',
        description:'Göreme Valley in Cappadocia is a historical marvel set against a unique geological backdrop, where centuries of wind and water have sculpted the landscape into whimsical formations. The valley is also famous for its open-air museums, underground cities, and the enchanting experience of hot air ballooning.',
        image:'https://assets.codepen.io/3685267/timed-cards-6.jpg'
    },
]

const _ = (id)=>document.getElementById(id)
const cards = data.map((i, index)=>`<div class="card" id="card${index}" style="background-image:url(${i.image})"  ></div>`).join('')



const cardContents = data.map((i, index)=>`<div class="card-content" id="card-content-${index}">
<div class="content-start"></div>
<div class="content-place">${i.place}</div>
<div class="content-title-1">${i.title}</div>
<div class="content-title-2">${i.title2}</div>

</div>`).join('')


const sildeNumbers = data.map((_, index)=>`<div class="item" id="slide-item-${index}" >${index+1}</div>`).join('')
_('demo').innerHTML =  cards + cardContents
_('slide-numbers').innerHTML =  sildeNumbers


const range = (n) =>
  Array(n)
    .fill(0)
    .map((i, j) => i + j);
const set = gsap.set;

function getCard(index) {
  return `#card${index}`;
}
function getCardContent(index) {
  return `#card-content-${index}`;
}
function getSliderItem(index) {
  return `#slide-item-${index}`;
}

function animate(target, duration, properties) {
  return new Promise((resolve) => {
    gsap.to(target, {
      ...properties,
      duration: duration,
      onComplete: resolve,
    });
  });
}

let order = [0, 1, 2, 3, 4, 5];
let detailsEven = true;

let offsetTop = 200;
let offsetLeft = 700;
let cardWidth = 200;
let cardHeight = 300;
let gap = 40;
let numberSize = 50;
const ease = "sine.inOut";

function init() {
  const [active, ...rest] = order;
  const detailsActive = detailsEven ? "#details-even" : "#details-odd";
  const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
  const { innerHeight: height, innerWidth: width } = window;
  offsetTop = height - 430;
  offsetLeft = width - 830;

  gsap.set("#pagination", {
    top: offsetTop + 330,
    left: offsetLeft,
    y: 200,
    opacity: 0,
    zIndex: 60,
  });
  gsap.set("nav", { y: -200, opacity: 0 });

  gsap.set(getCard(active), {
    x: 0,
    y: 0,
    width: window.innerWidth,
    height: window.innerHeight,
  });
  gsap.set(getCardContent(active), { x: 0, y: 0, opacity: 0 });
  gsap.set(detailsActive, { opacity: 0, zIndex: 22, x: -200 });
  gsap.set(detailsInactive, { opacity: 0, zIndex: 12 });
  gsap.set(`${detailsInactive} .text`, { y: 100 });
  gsap.set(`${detailsInactive} .title-1`, { y: 100 });
  gsap.set(`${detailsInactive} .title-2`, { y: 100 });
  gsap.set(`${detailsInactive} .desc`, { y: 50 });
  gsap.set(`${detailsInactive} .cta`, { y: 60 });

  gsap.set(".progress-sub-foreground", {
    width: 500 * (1 / order.length) * (active + 1),
  });

  rest.forEach((i, index) => {
    gsap.set(getCard(i), {
      x: offsetLeft + 400 + index * (cardWidth + gap),
      y: offsetTop,
      width: cardWidth,
      height: cardHeight,
      zIndex: 30,
      borderRadius: 10,
    });
    gsap.set(getCardContent(i), {
      x: offsetLeft + 400 + index * (cardWidth + gap),
      zIndex: 40,
      y: offsetTop + cardHeight - 100,
    });
    gsap.set(getSliderItem(i), { x: (index + 1) * numberSize });
  });

  gsap.set(".indicator", { x: -window.innerWidth });

  const startDelay = 0.6;

  gsap.to(".cover", {
    x: width + 400,
    delay: 0.5,
    ease,
    onComplete: () => {
      setTimeout(() => {
        loop();
      }, 500);
    },
  });
  rest.forEach((i, index) => {
    gsap.to(getCard(i), {
      x: offsetLeft + index * (cardWidth + gap),
      zIndex: 30,
      delay: 0.05 * index,
      ease,
      delay: startDelay,
    });
    gsap.to(getCardContent(i), {
      x: offsetLeft + index * (cardWidth + gap),
      zIndex: 40,
      delay: 0.05 * index,
      ease,
      delay: startDelay,
    });
  });
  gsap.to("#pagination", { y: 0, opacity: 1, ease, delay: startDelay });
  gsap.to("nav", { y: 0, opacity: 1, ease, delay: startDelay });
  gsap.to(detailsActive, { opacity: 1, x: 0, ease, delay: startDelay });
}

let clicks = 0;

function step() {
  return new Promise((resolve) => {
    order.push(order.shift());
    detailsEven = !detailsEven;

    const detailsActive = detailsEven ? "#details-even" : "#details-odd";
    const detailsInactive = detailsEven ? "#details-odd" : "#details-even";

    document.querySelector(`${detailsActive} .place-box .text`).textContent =
      data[order[0]].place;
    document.querySelector(`${detailsActive} .title-1`).textContent =
      data[order[0]].title;
    document.querySelector(`${detailsActive} .title-2`).textContent =
      data[order[0]].title2;
    document.querySelector(`${detailsActive} .desc`).textContent =
      data[order[0]].description;

    gsap.set(detailsActive, { zIndex: 22 });
    gsap.to(detailsActive, { opacity: 1, delay: 0.4, ease });
    gsap.to(`${detailsActive} .text`, {
      y: 0,
      delay: 0.1,
      duration: 0.7,
      ease,
    });
    gsap.to(`${detailsActive} .title-1`, {
      y: 0,
      delay: 0.15,
      duration: 0.7,
      ease,
    });
    gsap.to(`${detailsActive} .title-2`, {
      y: 0,
      delay: 0.15,
      duration: 0.7,
      ease,
    });
    gsap.to(`${detailsActive} .desc`, {
      y: 0,
      delay: 0.3,
      duration: 0.4,
      ease,
    });
    gsap.to(`${detailsActive} .cta`, {
      y: 0,
      delay: 0.35,
      duration: 0.4,
      onComplete: resolve,
      ease,
    });
    gsap.set(detailsInactive, { zIndex: 12 });

    const [active, ...rest] = order;
    const prv = rest[rest.length - 1];

    gsap.set(getCard(prv), { zIndex: 10 });
    gsap.set(getCard(active), { zIndex: 20 });
    gsap.to(getCard(prv), { scale: 1.5, ease });

    gsap.to(getCardContent(active), {
      y: offsetTop + cardHeight - 10,
      opacity: 0,
      duration: 0.3,
      ease,
    });
    gsap.to(getSliderItem(active), { x: 0, ease });
    gsap.to(getSliderItem(prv), { x: -numberSize, ease });
    gsap.to(".progress-sub-foreground", {
      width: 500 * (1 / order.length) * (active + 1),
      ease,
    });

    gsap.to(getCard(active), {
      x: 0,
      y: 0,
      ease,
      width: window.innerWidth,
      height: window.innerHeight,
      borderRadius: 0,
      onComplete: () => {
        const xNew = offsetLeft + (rest.length - 1) * (cardWidth + gap);
        gsap.set(getCard(prv), {
          x: xNew,
          y: offsetTop,
          width: cardWidth,
          height: cardHeight,
          zIndex: 30,
          borderRadius: 10,
          scale: 1,
        });

        gsap.set(getCardContent(prv), {
          x: xNew,
          y: offsetTop + cardHeight - 100,
          opacity: 1,
          zIndex: 40,
        });
        gsap.set(getSliderItem(prv), { x: rest.length * numberSize });

        gsap.set(detailsInactive, { opacity: 0 });
        gsap.set(`${detailsInactive} .text`, { y: 100 });
        gsap.set(`${detailsInactive} .title-1`, { y: 100 });
        gsap.set(`${detailsInactive} .title-2`, { y: 100 });
        gsap.set(`${detailsInactive} .desc`, { y: 50 });
        gsap.set(`${detailsInactive} .cta`, { y: 60 });
        clicks -= 1;
        if (clicks > 0) {
          step();
        }
      },
    });

    rest.forEach((i, index) => {
      if (i !== prv) {
        const xNew = offsetLeft + index * (cardWidth + gap);
        gsap.set(getCard(i), { zIndex: 30 });
        gsap.to(getCard(i), {
          x: xNew,
          y: offsetTop,
          width: cardWidth,
          height: cardHeight,
          ease,
          delay: 0.1 * (index + 1),
        });

        gsap.to(getCardContent(i), {
          x: xNew,
          y: offsetTop + cardHeight - 100,
          opacity: 1,
          zIndex: 40,
          ease,
          delay: 0.1 * (index + 1),
        });
        gsap.to(getSliderItem(i), { x: (index + 1) * numberSize, ease });
      }
    });
  });
}

async function loop() {
  await animate(".indicator", 2, { x: 0 });
  await animate(".indicator", 0.8, { x: window.innerWidth, delay: 0.3 });
  set(".indicator", { x: -window.innerWidth });
  await step();
  loop();
}

async function loadImage(src) {
  return new Promise((resolve, reject) => {
    let img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

async function loadImages() {
  const promises = data.map(({ image }) => loadImage(image));
  return Promise.all(promises);
}

async function start() {
  try {
    await loadImages();
    init();
  } catch (error) {
    console.error("One or more images failed to load", error);
  }
}

start()
Key Functions:
  • Data Array: The data array contains the content for each slide, including titles, descriptions, and background images.
  • Dynamic HTML Generation: The cards and cardContents variables dynamically generate HTML based on the data array, allowing for scalable and easily updatable content.
  • Interactivity: The script sets up the slider’s basic structure and prepares for additional logic to handle transitions and user interactions.

Conclusion

By combining HTML, CSS, and JavaScript, we’ve created a fully functional and visually appealing image slider. This slider is perfect for showcasing different destinations or any other content that benefits from a dynamic, interactive presentation. With the foundational code in place, you can further customize and enhance the slider to fit your specific needs.

4o

Add slider autoplay?

Related Posts

3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

×