Have trouble with my first JavaScript project

I’m trying to make a Netflix clone for my first project.

The problem -
When i reach the last slide and i click on the left arrow button, the image container slides into black space, and when i click on it again it resumes its normal behavior. How can it make it so it just slides normally instead of sliding into black space? A helping hand to steer me in the right direction would be greatly appreciated.

HTML

    <div class="netflix-text-description">
      <p>At Netflix, we want to entertain the world. Whatever your taste, and no matter where you live, we give you access to best-in-class TV series, documentaries, feature films and games. Our members control what they want to watch, when they want it, in one simple subscription. We’re streaming in more than 30 languages and 190 countries, because great stories can come from anywhere and be loved everywhere. We are the world’s biggest fans of entertainment, and we’re always looking to help you find your next favorite story.</p>
    </div>

    <div class="timeline-title">
      <h1>The Story of Netflix</h1>
    </div>

    <div class="timeline-container js-timeline-container">


       
    </div>  
  </div>
</section>

CSS

/* Timeline Images */

.timeline-container {
  position: relative;
  width: 100%;
  height: var(--height);
}

.timeline-slider {
  width: 100vw;
  position: relative;
}

.slider {
  position: relative;
  width: 100%;
  box-sizing: border-box;
}

.slider-track {
  height: var(--height);
  box-sizing: border-box;
  overflow: hidden;
}

.slider-images-container {
  width: 100%;
  position: relative;
  height: var(--height);
  display: flex;
  transition: 0.35s;
  left: 27%;
}

.timeline-wrapper {
  opacity: 0.2;
  margin-top: 80px;
  transition: 0.3s linear;
  position: relative;
  transform: scale(0.9);
  max-height: 400px;
  height: 100%;
}

.timeline-wrapper-active {
  opacity: 1;
  transform: scale(var(--scale)) translateY(20px);
  z-index: 2;
  transition: 0.3s linear;
}

.timeline-wrapper:first-of-type img {
  padding-left: 50px;
}

.timeline-images {
  width: 370px;
  margin-top: 16px;
  position: relative;
  max-width: 100%;
  max-height: 100%;
}

.timeline-images::after {
  display: block;
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  inset: 0px;
  background-color: rgb(0, 0, 0);
  opacity: var(--opacity);
}

.timeline-image {
  width: 100%;
  object-fit: cover;
}

/* Left and Right Slider Buttons */

.slide-left {
  border: 1px solid red;
  font-size: 25px;
  background-color: red;
  color: white;
  padding: 20px 23px;
  cursor: pointer;
  position: absolute;
  top: 50%;
  z-index: 5;
}

.slide-left:disabled,
.slide-right:disabled {
  opacity: 0.3;
  cursor: default;
}

.slide-left:disabled:hover,
.slide-right:disabled:hover {
  background-color: red;
}

.slide-right {
  border: 1px solid red;
  font-size: 25px;
  background-color: red;
  color: white;
  padding: 20px 23px;
  cursor: pointer;
  position: absolute;
  right: 0;
  top: 50%;
  z-index: 5;
}

.slide-right:hover,
.slide-left:hover {
  background-color: black;
}

JavaScript

const timelineImages = [
  {
  slide: 1,
  image: "https://images.ctfassets.net/4cd45et68cgf/3GKNeghrWyZuILcmSCUyhc/ff1d650ebc53aefdf929ded05fe79657/1997.png?w=600",
  year: 1997,
  description: "Reed Hastings and Marc Randolph have an idea to rent DVDs by mail. They test the concept by mailing themselves a DVD. The DVD arrives intact, and the idea for Netflix is born."
  },{
    slide: 2,
    image: "https://images.ctfassets.net/4cd45et68cgf/49h4VMqnw4LPmzFR1rSErW/fe18b9caa12606f5730a53634ce8b254/companytimeline-1998-new.jpg?w=600",
    year: 1998,
    description: "Netflix.com, the first DVD rental and sales site, is launched."
  },{
    slide: 3,
    image: "https://images.ctfassets.net/4cd45et68cgf/5E6RhvNlPUMP13UfXWJ1Oe/ff5cd573739594c72b77341684a2bddc/1999.png?w=600",
    year: 1999,
    description: "The Netflix subscription service debuts, offering members unlimited DVD rentals without due dates, late fees, or monthly rental limits."
  },{
    slide: 4,
    image: "https://images.ctfassets.net/4cd45et68cgf/5FbKhRPFjP9pstOJ9tosHk/93dcf9661d66eff2bf9f731d936041e9/2000.png?w=600",
    year: 2000,
    description: "A personalized movie recommendation system is introduced, using members’ ratings on past titles to accurately predict future choices."
  },{
    slide: 5,
    image: "https://images.ctfassets.net/4cd45et68cgf/4wF7y1a7FwFBlvyIhPyzNx/106d0ab7d69fa47b7002ee754bdb37ab/2002_1.jpg?w=600",
    year: 2002,
    description: "Netflix makes its initial public offering (IPO), at a selling price of $1 a share under the NASDAQ ticker NFLX."
   }
   ,{
    slide: 6,
    image: "https://images.ctfassets.net/4cd45et68cgf/3iMJ73YNAhV6EvXoJhYLEo/6ac0b4e6c8423fc8669c44d5b76b4b5c/2003_1.jpg?w=600",
    year: 2003,
    description: "Netflix is issued a patent by the US Patent & Trademark Office to cover its subscription rental services as membership surpasses 1 million."
  },{
    slide: 7,
    image: "https://images.ctfassets.net/4cd45et68cgf/6CrM2vzerGJRhnBUOkumLF/ac506868d6876d99bb20b5a80c74539e/companytimeline-2005-new.jpg?w=600",
    year: 2005,
    description: "The Profiles feature launches, allowing members to create different lists for different users and/or different moods."
  },{
    slide: 8,
    image: "https://images.ctfassets.net/4cd45et68cgf/77sOMDenC0FkEWyIm7duiz/bd67c63a59d5ba56b6a6d2e8b1aa6f39/2006_1.jpg?w=600",
    year: 2006,
    description: "Membership grows to 5 million."
  },{
    slide: 9,
    image: "https://images.ctfassets.net/4cd45et68cgf/5AsBxkNnvLLiqFGH3z10Sv/f5287d6f4198c7b830ef98977405880f/2007.png?w=600",
    year: 2007,
    description: "Streaming is introduced, allowing members to instantly watch series and films."
  },{
    slide: 10,
    image: "https://images.ctfassets.net/4cd45et68cgf/5kdKFWSPI79FiBhvZII6dt/fbe3d1e4fb3cf5f7e89806d62ccf64d4/2008.png?w=600",
    year: 2008,
    description: "Netflix partners with consumer electronics brands to allow streaming on Xbox 360, Blu-ray players and TV set-top boxes."
  },{
    slide: 11,
    image: "https://images.ctfassets.net/4cd45et68cgf/4fmNc15jt0ee61uCz5gOgP/079d44b6b46f02672e70e9d3a98bee60/2009.png?w=600",
    year: 2009,
    description: "After nearly three years and 40,000 submissions, the $1 million Netflix Prize is awarded to the team Bellkor's Pragmatic Chaos for improving the accuracy of recommendations by 10%. Streaming partnerships expand to internet connected TVs as membership surpasses 10 million. The Netflix Culture Deck is published."
  },{
    slide: 12,
    image: "https://images.ctfassets.net/4cd45et68cgf/6OcT8vmejBT1d1UBZlNBD6/ec3f5ba660eaf1741d404cc53fdc05b7/2010.png?w=600",
    year: 2010,
    description: "Netflix arrives in Canada and streaming launches on mobile devices. The first dedicated kids experience debuts on streaming."
  },{
    slide: 13,
    image: "https://images.ctfassets.net/4cd45et68cgf/49MAZ95UjLhBPmiLhdvYZX/d10feacbc97db061ea71e6a2818ba05f/2011.png?w=600",
    year: 2011,
    description: "Netflix launches in Latin America and the Caribbean. The first Netflix button appears on remote controls."
  },{
    slide: 14,
    image: "https://images.ctfassets.net/4cd45et68cgf/5WWxaIZFa6WgUXzBiQ5Dxj/ff23dacc2455aef6f2841b0322098369/companytimeline-2012new.jpg?w=600",
    year: 2012,
    description: "Membership reaches 25 million members, and expands into the United Kingdom, Ireland and the Nordic Countries. Netflix ventures into stand-up specials with 'Bill Burr: You People Are All the Same.'"
  },{
    slide: 15,
    image: "https://images.ctfassets.net/4cd45et68cgf/6qfh92PcB3qYHRfUVLA6xT/cc07069e265361ca46f8aca9d2dcb043/companytimeline-2013-new_copy_1.jpg?w=600",
    year: 2013,
    description: "'House of Cards,' 'Hemlock Grove,' 'Arrested Development' and 'Orange Is the New Black' usher in the first slate of original series programming. 'House of Cards' goes on to win three Primetime Emmy awards - the first for an internet streaming service. The Profiles and My List features debut on streaming."
  },{
    slide: 16,
    image: "https://images.ctfassets.net/4cd45et68cgf/5tMEKGByZMi2yVZQdows6N/f4207f42679a89d9871bf1e73f18d105/companytimeline-2014new.jpg?w=600",
    year: 2014,
    description: "Membership surpasses 50 million and extends to Austria, Belgium, France, Germany, Luxembourg and Switzerland. Netflix begins streaming in 4K Ultra HD."
  },{
    slide: 17,
    image: "https://images.ctfassets.net/4cd45et68cgf/2JXwkOk00mTTVOBiNkybja/ab3466dcf6317a7ac5860ae4b44d9874/2015.png?w=600",
    year: 2015,
    description: "Netflix's first original feature film ('Beasts of No Nation'), first non-English original series ('Club de Cuervos') and first Asian original ('Terrace House') debut. Membership extends to Australia, Cuba, Italy, Japan, Spain and New Zealand. Audio descriptions for the visually impaired launch with 'Daredevil.'"
  },{
    slide: 18,
    image: "https://images.ctfassets.net/4cd45et68cgf/ak3No94rWAFzgTQ5psied/03d8fb2b4c57159ba6292755eeeebfd3/CompanyTimeline-2016.jpg?w=600",
    year: 2016,
    description: "Netflix expands to 130 new countries, bringing the service to members in more than 190 countries and 21 languages around the world. The Download feature is added for offline and on-the-go viewing."
  },{
    slide: 19,
    image: "https://images.ctfassets.net/4cd45et68cgf/U56yFUe7emjAJ5SFOHavt/31662e65bc0ee85c1cc1cd6a74fc05c8/2017.png?w=600",
    year: 2017,
    description: "Membership hits 100 million members globally. Netflix wins its first Academy Award®, for 'The White Helmets.' The introduction of interactive storytelling and the Skip Intro button gives members more choices to tailor their viewing experience."
  },{
    slide: 20,
    image: "https://images.ctfassets.net/4cd45et68cgf/4YE2LCPWYflgFT99P6luy6/0df36eec1dfffe02fae72b736c5a6423/companytimeline-2018-new.jpg?w=600",
    year: 2018,
    description: "Netflix is the most-nominated studio at the Emmys®, winning 23 for series including 'GLOW,' 'Godless' and 'Queer Eye.' PIN protection is rolled out as part of several parental control enhancements."
  },{
    slide: 21,
    image: "https://images.ctfassets.net/4cd45et68cgf/6iogKYj6GafoQoAPOkhJ4r/01f856bd6265af47af84c9e5ee464727/ROMA_06587_010R__1_.JPG?w=600",
    year: 2019,
    description: "Netflix wins four Academy Awards®, for 'ROMA' and 'Period. End of Sentence,' and debuts its first original animated film with 'Klaus.' New production hubs open in London, Madrid, New York and Toronto. 'Bandersnatch' wins the first major Emmy® for an interactive title."
  },{
    slide: 22,
    image: "https://images.ctfassets.net/4cd45et68cgf/72eCVMvLIH4ImDCuZuzwZx/5c2c4e8ebd80f339a069a8a8848139c8/newtop10.jpg?w=600",
    year: 2020,
    description: "Top 10 lists debut, allowing members to see what’s popular for the first time. Netflix is the most-nominated studio at the Academy Awards® and Emmys®. The Hardship Fund launches to aid creative community workers impacted by COVID-19, and two percent of our cash holdings move to financial institutions supporting Black communities."
  },{
    slide: 23,
    image: "https://images.ctfassets.net/4cd45et68cgf/2I1iWx5wmGph8qXaIWUdGy/0bee4d7a51048e9685f9aec50f20b795/Android_Collage_1080x1080_APAC_Singapore_En.jpg?w=600",
    year: 2021,
    description: "Membership surpasses 200 million. Netflix releases its first-ever film and series diversity study, in conjunction with the USC Annenberg Inclusion Initiative, and announces plans to reach net-zero greenhouse gas emissions by the end of 2022. Netflix launches mobile games."
  },{
    slide: 24,
    image: "https://images.ctfassets.net/4cd45et68cgf/4F7XFh8GsIYcTDT0N0AvPj/68ca84cc2240db8478b559a8c7ae5942/NIAJ_Intro_042722_01004.jpg?w=600",
    year: 2022,
    description: "Netflix Is a Joke: The Festival, our biggest live, in-person event takes place in Los Angeles, spanning 11 days and 295 shows. Netflix rolls out spatial audio to bring the cinematic experience to any device and adds Category Hubs for TV. A new lower-price ad-supported plan launches in November in 12 countries."
  },{
    slide: 25,
    image: "https://images.ctfassets.net/4cd45et68cgf/6GwfUKfg7Ni76NfMJLfusz/616b7803027a7fc1bafd8ddbd6608fad/chrisrock.jpg?w=600",
    year: 2023,
    description: "Netflix begins live streaming with the Emmy®-nominated stand-up special 'Chris Rock: Selective Outrage.’ The studio wins six Academy Awards®, including best animated feature for 'Guillermo del Toro's Pinocchio' and four for 'All Quiet on the Western Front' — the most wins for any Netflix film. The company also produces its first stage play, 'Stranger Things: The First Shadow,' which opens on London's West End."
  }
];

timelineImages.forEach(timeline => {
  let currentSlide = 1;
  
  const timelineContainerHTML = document.querySelector(".js-timeline-container");

  timelineContainerHTML.innerHTML = `
    <button class="slide-left js-slide-left" type="button">
      <span>
        <i class="fa fa-arrow-left" aria-hidden="true"></i>
      </span>
    </button>
    <button class="slide-right js-slide-right" type="button">
      <span>
        <i class="fa fa-arrow-right" aria-hidden="true"></i>
      </span>
    </button>

    <div class="timeline-slider">
      <div class="slider">
        <div class="slider-track">
          <div class="slider-images-container js-slider-images-container">
            ${getTimelineImages()}
          </div>
        </div>
      </div>
    </div>
    <div class="timeline-bar-container">
      <div class="timeline-bar">
        <div class="bar-buffer"></div>
        <div class="bar-marker"></div>
        <div class="bar-marker"></div>
        <div class="bar-progress"></div>
        <div class="bar-thumb"></div>
      </div>
    </div>

    <div class="timeline-text">
      <p>${timeline.description}</p>
    </div>
  `;

  const sliderImgContainer = document.querySelector(".js-slider-images-container");
  const imageWrapper = document.querySelectorAll(".js-timeline-wrapper");
  
  let counter = 0;
  imageWrapper[counter].classList.add("timeline-wrapper-active");  

  const slideLeft = document.querySelector(".js-slide-left");
  slideLeft.disabled = true;
  slideLeft.addEventListener("click", (e) => {
    counter--;
    currentSlide--;
    timelineSlider();
  });
  
  const slideRight = document.querySelector(".js-slide-right");
  slideRight.addEventListener("click", (e) => {
    counter++
    currentSlide++;
    timelineSlider();
  });

  function timelineSlider() {
    imageWrapper.forEach((wrapper, i) => {
      const wrapperWidth = wrapper.getBoundingClientRect().width;
      const sliderImagesContainerWidth = sliderImgContainer.getBoundingClientRect().width;
      
      sliderImgContainer.style.transform = `translate3d(-${counter * wrapperWidth}px, 0px, 0px)`;
      sliderImgContainer.style.width = `${sliderImagesContainerWidth}`
      wrapper.style = `margin-right: -38px`;
    });

    for (let i = 0; i < imageWrapper.length; i++) {
      imageWrapper[i].classList.remove("timeline-wrapper-active");
    }
    
    setTimeout(() => {
      imageWrapper[counter].classList.add("timeline-wrapper-active");
    }, 400);

    slideRight.ariaLabel=`slide ${currentSlide} of ${timeline.slide}`;
    slideLeft.ariaLabel=`slide ${currentSlide} of ${timeline.slide}`;

    currentSlide < 2 ? slideLeft.disabled = true : slideLeft.disabled = false;
    currentSlide <= imageWrapper.length ? slideRight.disabled = false : slideRight.disabled = true;

    currentSlide > 1 ? slideLeft.disabled = false : slideLeft.disabled = true;
    currentSlide >= imageWrapper.length ? slideRight.disabled = true : slideRight.disabled = false;
  }

  function getTimelineImages() {
    let timelineImagesHTML = "";
  
    timelineImages.forEach((timeline) => {
      timelineImagesHTML += `
        <article class="timeline-wrapper js-timeline-wrapper"
          data-slide="${timeline.slide}">
          <div class="timeline-images">
            <img class="timeline-image" src="${timeline.image}">
          </div>
        </article>
      `;
    });
    
    return timelineImagesHTML;
  }
});

You need to set a positive z-index on the images.

console.log(counter * wrapperWidth, "CONTAINER", sliderImagesContainerWidth, "WRAPPER", wrapperWidth)

I added this line at the end of forEach loop and noticed that the WRAPPER value for one element is different than the normal 333 like 370 or other values.

Using the inspector, I found that the element with timeline-wrapper-active is 370 in width and other normal images are 333 in width.

The reason for this is that setTimeout() adds the class to the current element that counter points to after a delay (400 milliseconds). Since it will not pause your code execution until that class is added, it will cause some issues calculating how much sliderImgContainer should translate3d by the time you reach the last image. This is because the class includes styling that increases the element’s width.


Instead, I made wrapperWidth always use the width of the first image:

That should fix the issue.

It took some time to figure it out, so I hope my explanation makes sense.

1 Like

This worked! I was stuck on this problem for a month and i had decided not to watch any tutorials when i started this project.
I didn’t know forEach accepted more then 2 parameters but this new piece of information will surely help me more further down the line.
Really appreciative of this community and it’s members, thank you once again.

1 Like

I’m unsure if i did it right, i added z-index: 10; to the images first, and then the image container but can’t see visible changes

I’ve been testing and now the first slide is scrolling by the width of 536.5 and the 2nd slide is no longer centered whilst the other slides all scroll by 333 but still remains centered. I tried to apply first of type in CSS but that didn’t work. Is there a way to fix this?

This seems to fix it but it feels wrong and i don’t think its the right way to do it

if (counter === 23) {
const wrapperWidthReverse = arr[0].getBoundingClientRect().width;
sliderImgContainer.style.transform = translate3d(-${counter * wrapperWidthReverse}px, 0px, 0px);
}

— code removed —

With the existing code, I don’t get this value.

If you’ve made new changes, could you post them ?

The if condition you added doesn’t seem to do anything different from what’s already there in the forEach loop. These lines should already be executing for all images, so why are you specifically using counter === 23?

The code below actually solves my problem, however it somehow changes how the first slide slides, meaning the 2nd image is no longer centered on the page.

imageWrapper.forEach((wrapper, i, arr) => {
      const wrapperWidth = arr[0].getBoundingClientRect().width;

With this code however it solves both problems of the last slide sliding into black space, and the 2nd slide not centering itself. But the code feels wrong and bad practice and i’m also not sure why it works.

imageWrapper.forEach((wrapper, i, arr) => {
      const wrapperWidth = wrapper.getBoundingClientRect().width;
      const sliderImagesContainerWidth = sliderImgContainer.getBoundingClientRect().width;
      
      sliderImgContainer.style.transform = `translate3d(-${counter * wrapperWidth}px, 0px, 0px)`;
      sliderImgContainer.style.width = `${sliderImagesContainerWidth}`
      wrapper.style = `margin-right: -38px`;

      if (counter === 23) {
        const wrapperWidthReverse = arr[0].getBoundingClientRect().width;
        sliderImgContainer.style.transform = `translate3d(-${counter * wrapperWidthReverse}px, 0px, 0px)`;
      }
    });

The problem with forEach is that on each call to the timelineSlider function it will iterate through all images, even though the only image that will affect the movement of the slider container (based on its width) is the last one, since it will be the last iteration.

When you reach that last image (by the slide right button), you will be using its bigger width since it will have the timeline-wrapper-active class. That’s why it will move into black space only at the last image.


To get rid of those redundant iterations and make your code easier to read and understand, you can use the counter being incremented or decremented before calling timelineSlider function to select the next or previous image, and just use its width to move the slider.

By removing the entire forEach loop and use only these two lines:

let nextImg = imageWrapper[counter];
sliderImgContainer.style.transform = `translate3d(-${counter * nextImg.getBoundingClientRect().width}px, 0px, 0px)`;

For wrapper.style = 'margin-right: -38px';, you can do it by CSS.


I know this isn’t what you needed to center your image as you want, but you were centering it by changing how much the slider would move.

1 Like

This is precisely what i needed to hear.

  • I should’ve of noticed that one of the elements width was inconsistent with the rest and tried something else.
  • My understanding of forEach was also insufficient, as i thought forEach was used to loop through arrays and querySelectorAll in order to access the elements, and thus i tunnel visioned thinking it was the only way to access imageWrapper and get the width of each element.

Once again, really appreciative for the guiding words and thank you for assistance. I feel a little bit more confident now!

1 Like

It is great that you solved the challenge, but instead of posting your full working solution, it is best to stay focused on answering the original poster’s question(s) and help guide them with hints and suggestions to solve their own issues with the challenge.

We are trying to cut back on the number of spoiler solutions found on the forum and instead focus on helping other campers with their questions and definitely not posting full working solutions.

1 Like