How To Program A Controllable Sliding Banner with JavaScript

Are You Comfortable with HTML and CSS?

Especially if you're already comfortable with HTML and CSS... but if you've only dabbled slightly with using JavaScript, then the following video tutorial should give you a good insight into the power of combining together our three essential programming languages.

I programmed this controllable sliding banner several years ago, by teaching myself website development via the numerous online learning resources available to us. It takes full advantage of how JavaScript can interact with HTML and CSS. On my journey to learning website development I stumbled across jQuery, which is a popular programming library designed to make it easier to accomplish complex JavaScript tasks, by reducing the amount of code we need to write.

Programming Code Snippets Explained

The above tutorial walks you through the entire process of how to get the above controllable banner up and running for yourself, which you can then incorporate into your own site. I explain what each part of the code is doing, and I also demonstrate the results as we go along.

The code for the sliding banner is below. Watching the video will help significantly in understanding what each part of the code is doing, and the demonstrations throughout the video will help you see if everything is working correctly... and it will also support the channel 🙂

Sliding Banner PT1 - HTML Code

This is the HTML required for the banner. It's not a lot of code, and as demonstrated in the video, it'll probably be easier for you if you use the images from this site. Once you get it up and running, then of course you'll want to use your own images.

One of the most important things to keep in mind is that the sliding banner works by setting the position property of the sliding images to absolute, but then also offsets the current image to the right ready for sliding left, and then resets all image positions to the centre after the translation is complete, and so the z-index property plays an important roll.

The CSS is at the bottom of this page.

HTML Code
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.   <script src="jquery-3.6.4.min.js"></script>
  5.   <link rel="stylesheet" type="text/css" href="Responsive Controllable Banner.css" />
  6.   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. </head>
  8.  
  9. <body>
  10.   <div id="company_logo">
  11.     <img src="Creative-programming-logo.png" />
  12.   </div>
  13.  
  14.   <div id="slide_divs_container">
  15.     <div class="slide_div">
  16.       <div class="slide_top_text">Blender model design – Manipulating vertices by using the proportional editing tool</div>
  17.       <img class="banner_image" src="Blender-transforming-vertices.jpg" />
  18.     </div>
  19.     <div class="slide_div">
  20.       <div class="slide_top_text">OpenGL GLFW – Creating our very 1st display window – Basic shapes and transparency</div>
  21.       <img class="banner_image" src="GLFW-hello-graphics-window.jpg" />
  22.     </div>
  23.     <div class="slide_div">
  24.       <div class="slide_top_text">GLM Maths – Matrix transformations used to create 3D spinning orbiting cubes</div>
  25.       <img class="banner_image" src="OpenGL-orbiting-spinning-cubes.jpg" />
  26.     </div>
  27.  
  28.     <a id="prev" data-value="-1">&#10094;</a>
  29.     <a id="next" data-value="1">&#10095;</a>
  30.   </div>
  31.  
  32.   <div class="controls_container">
  33.     <div class="dot_button" data-value="0"></div>
  34.     <div class="dot_button" data-value="1"></div>
  35.     <div class="dot_button" data-value="2"></div>
  36.  
  37.     <div class="play_banner" id="play_pause" onclick="">Play/Pause</div>
  38.  
  39.     <div id="speed_indicator">
  40.       <input id="scroll_speed" class="control_sliders" type="range" min="10" max="100" value="60" />
  41.     </div>
  42.  
  43.     <div id="duration_indicator">
  44.       <input id="pause_duration" class="control_sliders" type="range" min="10" max="100" value="40" />
  45.     </div>
  46.   </div>

Sliding Banner PT2 - Stylesheets & initiate_slides()

All of the code that follows is a mixture of JavaScript and jQuery, but remember that jQuery is essentially just JavaScript, and so using them interchangeably is perfectly fine, and even common practice for many programmers.

The following code creates two new stylesheets and appends them to the document head. The initiate_slides() function makes the first slide visible by setting its z-index value. jQuery is then used to set all the images to the width of the slide division container, and then set the container height to the height of the image parent division. The transform and background position rules are then added to the stylesheets, the scroll speed and pause duration are set, the dot buttons made visible, and finally the play_pause_banner() function is called.

  // Declare Stylesheets
  var style_slides_new_element = document.createElement("style");
  var style_controls_new_element = document.createElement("style");

  document.head.appendChild(style_slides_new_element);
  document.head.appendChild(style_controls_new_element);

  var stylesheet_slides = style_slides_new_element.sheet;
  var stylesheet_controls = style_controls_new_element.sheet;

  // Insert Stylesheet Rule
  function insert_rule_slides(name, frames) {
    stylesheet_slides.insertRule("@keyframes " + name + "{" + frames + "}", 0); // insert rule at beginning of stylesheet
  }
  function insert_rule_controls(name, frames) {
    stylesheet_controls.insertRule("@keyframes " + name + "{" + frames + "}", 0);
  }

  // Initiate Banner
  var scroll_speed = 0;
  var pause_duration = 0;

  initiate_slides();

  function initiate_slides() {
    window.addEventListener("load", function () {
      $(".slide_div:eq(" + 0 + ")").css("z-index", $(".slide_div").length); // set 1st banner to highest z-index

      var container_width = $("#slide_divs_container").width(); // note: jQuery might add in the "px" itself
      $(".banner_image").css("width", container_width + "px"); // set all banner images widths to parent container width

      var container_height = $(".slide_div:eq(" + 0 + ")").height(); // set container height to images parent div height
      $("#slide_divs_container").css("height", container_height);

      var banner_width_0 = $(".slide_div:eq(" + 0 + ")").width(); // using "0" but could use any index here

      insert_rule_slides("slide_banner_left", "100% { transform: translateX(" + (-banner_width_0) + "px" + ") }");
      insert_rule_slides("slide_banner_right", "100% { transform: translateX(" + (banner_width_0) + "px" + ") }");

      var indicator_width = $("#duration_indicator").width();
      insert_rule_controls("indicator", "100% { background-position: " + indicator_width + "px " + "0" + "; }");

      scroll_speed = 90 / $("#scroll_speed").val();
      pause_duration = 100 / $("#pause_duration").val();

      document.getElementsByClassName("dot_button")[0].className += " dot_button_active";

      play_pause_banner(); // start playing banner
    });
  }

Sliding Banner PT3 - play_pause_banner()

This function is called whenever the play/pause button is clicked on. The banner_playing variable toggles between being true and false. The else-statement executes when the banner is paused, which clears the time interval for calling the banner_direction() function thereby stopping the banner animating/sliding, deletes the controls stylesheet rule for animating the colour gradient of the right slider, and then changes the button colour to red by adding the pause_banner CSS class.

The if-statement is executed when the banner is set to play, which not surprisingly is the opposite of the else-statement. The controls stylesheet rule is re-added, the time interval is re-started, and the pause_banner CSS class is removed.

  // Play & Pause Control   
  var banner_playing = false;
  var pause_duration_handle;

  $("#play_pause").on("click", function () {
    play_pause_banner();
  });

  function play_pause_banner()
  {
    if (banner_playing == false)
    {
      banner_playing = true;

      // insert animation rule at the end of the stylesheet & also start running banner_direction
      stylesheet_controls.insertRule("#duration_indicator { animation: indicator " + pause_duration + "s linear infinite; }", stylesheet_controls.cssRules.length);
      pause_duration_handle = setInterval(banner_direction, pause_duration * 1000, 1);

      $("#play_pause").removeClass("pause_banner");
      console.log("banner_playing: " + banner_playing);
    }
    else
    {
      banner_playing = false;
      clearInterval(pause_duration_handle);

      // delete rule 2 of 2 only
      if (stylesheet_controls.cssRules.length == 2)
          stylesheet_controls.deleteRule(stylesheet_controls.cssRules.length - 1);

      $("#play_pause").addClass("pause_banner");
      console.log("banner_playing: " + banner_playing);
    }
  }

Sliding Banner PT4 - Slider Control Functions

The scroll-speed function body simply sets the scroll_speed variable according to the left slider position, which is then used within the process_sliding_banner() function

The pause-duration function body sets the pause_duration variable in the same manner, but then also clears the banner-direction time interval to stop the animation, deletes the controls stylesheet rule, waits for the stylesheet to update, re-inserts the stylesheet rule, and re-starts the animation.

  // Scroll Speed (Left Slider)     
  $("#scroll_speed").on("change", function ()
  {
    scroll_speed = 90 / $(this).val();
    console.log("scroll_speed: " + scroll_speed);
  });

  // Pause Duration (Right Slider)   
  $("#pause_duration").on("change", function ()
  {
    pause_duration = 100 / $(this).val();
    console.log("pause_duration: " + pause_duration);

    clearInterval(pause_duration_handle); // this immediately stops banner_direction() from running again

    // delete rule 2 of 2 only the same as in 'Play & Pause Control'
    if (stylesheet_controls.cssRules.length == 2)
    {
      stylesheet_controls.deleteRule(stylesheet_controls.cssRules.length - 1);
      setTimeout(function () // allow time for stylesheet deleteRule as above to complete to avoid clashing with insertRule as below...
      {      
        // re-insert the 2nd rule with new pause_duration
        stylesheet_controls.insertRule("#duration_indicator { animation: indicator " + pause_duration + "s linear infinite; }", stylesheet_controls.cssRules.length);
        pause_duration_handle = setInterval(banner_direction, pause_duration * 1000, 1);
      }, 150);
    }
  });

Sliding Banner PT5 - banner_direction()

The banner_direction() function simply calls the process_sliding_banner() function, passing in the current direction.

Remember from further up that the banner_direction() function is the one called automatically via setInterval(). However, it's also called whenever the left or right arrow is clicked on. Typically you'll only manually select a slide via the dot buttons or arrows when the banner is paused, but you can for example make the banner slide in either direction even when playing, as long as you click on an arrow during the pause duration period.

  // Process Direction Selection  
  var slide_index = 0;
  var animation_running = false;

  function banner_direction(index)
  {
    if (animation_running == false)
    {
      animation_running = true;
      slide_index += index;

      if (index == 1)
        process_sliding_banner("slide_left");
      if (index == -1)
        process_sliding_banner("slide_right");

      console.log("Process Direction Selection");
    }
  }

Sliding Banner PT6 - Manual Slide Selection

As mentioned just above in PT5, the left and right arrows (i.e. #prev & #next) simply call the banner_direction() function with the arrow button's value, thereby indirectly calling the process_sliding_banner() function.

However, whenever a dot button is clicked on, the code then calls the process_sliding_banner() function directly, passing in the dot button's value.

  // Manual Slide Selection    
  var button_curr_index = 0;

  $("#prev, #next").on("click", function () {
      banner_direction($(this).data("value"));
    });

  $(".dot_button").on("click", function ()
  {        
    button_curr_index = slide_index;
    slide_index = $(this).data("value");

    process_sliding_banner("button");
  });

Sliding Banner PT7 - process_sliding_banner()

The process_sliding_banner() function's first two if-statements keep the slide position index value in a loop, and then the dot button styling and the image slide division z-index value are set accordingly.

Next, if the image is set to slide left, the slide position index value is once again handled accordingly; the set_slides_z_index() function is called, which sets the z-index of each image slide division accordingly; the image that's about to slide to the centre is moved right ready for sliding left, and then the animation rule being set to the current scroll-speed is inserted into the stylesheet, which starts the animation.

Finally, the slides_back_to_centre() function is called, which deletes the animation rule and moves all the slides back to centre, but only after the current animation has completed.

Sliding right is exactly the same as sliding left but with the values reversed as required.

  // Process Sliding Banner    
  var curr_index;
  var next_index;

  function process_sliding_banner(method)
  {
    var slides = document.getElementsByClassName("slide_div");
    var dots = document.getElementsByClassName("dot_button");

    if (slide_index > slides.length - 1) {
      slide_index = 0;
    }
    if (slide_index < 0) {
      slide_index = slides.length - 1;
    }

    for (var i = 0; i < dots.length; ++i) {
      dots[i].className = dots[i].className.replace(" dot_button_active", "");
    }
    dots[slide_index].className += " dot_button_active";
    
    if (method == "button")
    {         
      $(".slide_div").css("z-index", "1");
      $(".slide_div:eq(" + button_curr_index + ")").css("z-index", "2");
      $(".slide_div:eq(" + slide_index + ")").css("z-index", "3");
      $(".slide_div:eq(" + slide_index + ")").addClass("image_fade");
      $(".slide_div:eq(" + slide_index + ")").on(
        "animationend",
        function () {
          $(this).removeClass("image_fade");
        }
      );
    }
    if (method == "slide_left") // make the selected slide move left
    {
      curr_index = slide_index - 1;
      next_index = slide_index + 1;

      if (curr_index == -1)
        curr_index = slides.length - 1;

      if (next_index > slides.length - 1)
        next_index = 0;

      set_slides_z_index() // set z-index

      var curr_width = $(".slide_div:eq(" + curr_index + ")").width();
      $(".slide_div:eq(" + slide_index + ")").css("left", curr_width + "px"); // move newly selected banner to the right ready for left slide

      // slide banner to the left... insert rule at the end of the stylesheet
      stylesheet_slides.insertRule(".slide_div { animation: slide_banner_left " + scroll_speed + "s; }", stylesheet_slides.cssRules.length);

      slides_back_to_centre() // slides back to centre
    }
    
    if (method == "slide_right") // make the selected slide move right
    {         
      curr_index = slide_index + 1;
      next_index = slide_index - 1;

      if (curr_index > slides.length - 1) curr_index = 0;
      if (next_index == -1) next_index = slides.length - 1;

      set_slides_z_index() // set z-index
      
      var curr_width = $(".slide_div:eq(" + curr_index + ")").width();
      $(".slide_div:eq(" + slide_index + ")").css("left", -curr_width + "px"); // -ve 'curr_width' move newly selected banner to the left ready for right slide

      // slide banner to the right... insert rule at the end of the stylesheet
      stylesheet_slides.insertRule(".slide_div { animation: slide_banner_right " + scroll_speed + "s; }", stylesheet_slides.cssRules.length);

      slides_back_to_centre() // slides back to centre
    }

    function set_slides_z_index()
    {
      $(".slide_div:eq(" + next_index + ")").css("z-index", "1");
      $(".slide_div:eq(" + curr_index + ")").css("z-index", "2");
      $(".slide_div:eq(" + slide_index + ")").css("z-index", "3");
    }

    function slides_back_to_centre()
    {
      $(".slide_div:eq(" + 0 + ")").on("animationend", function () // using "0" but could use any slide_div index here
      {
        stylesheet_slides.deleteRule(stylesheet_slides.cssRules.length - 1); // delete rule from the end of the stylesheet

        $(".slide_div").css("left", "0");
        $(".slide_div:eq(" + 0 + ")").off("animationend");

        animation_running = false;
      });
    }
  }

Sliding Banner PT8 - Window Resize Event

The window on-resize event performs some of the same tasks as we've already looked at, which essentially is to set the width of the three images and the height of each image slide division, and to set the transformation translateX rule with the new banner width.

  // Window Resized    
  $(window).on("resize.set_banner", function ()
  {
    var container_width = $("#slide_divs_container").width();
    $(".banner_image").css("width", container_width + "px"); // set all banner images widths to parent container width

    var container_height = $(".slide_div:eq(" + 0 + ")").height(); // set container height to images parent div height
    $("#slide_divs_container").css("height", container_height + "px");

    console.log("window resized (before deleteRule) - stylesheet_slides rules remaining: " + stylesheet_slides.cssRules.length);
    
    // delete both transform rules
    stylesheet_slides.deleteRule(0); // a for-loop would unwantedly delete the left or right animation rule which is then added back in at...
    stylesheet_slides.deleteRule(0); // position 0 in 'Process Sliding Banner' whereas it should always be at position 3 not 0
    
    console.log("window resized (after deleteRule) - stylesheet_slides rules remaining: " + stylesheet_slides.cssRules.length);
    
    var banner_width_0 = $(".slide_div:eq(" + 0 + ")").width(); // using "0" but could use any slide_div index here
    
    insert_rule_slides("slide_banner_left", "100% { transform: translateX(" + (-banner_width_0) + "px" + ") }");
    insert_rule_slides("slide_banner_right", "100% { transform: translateX(" + (banner_width_0) + "px" + ") }");

    console.log("window resized (after insertRules) - stylesheet_slides rules remaining: " + stylesheet_slides.cssRules.length);
  });

Sliding Banner PT9 - CSS Code

The vast majority of the CSS for the sliding banner isn't being set by JavaScript, as you can see below. You might notice the @keyframes image_fade {} at the bottom... that doesn't need setting by JavaScript, simply because there are no size changes involved.

Other than that there's not much to say about it, apart from the overflow: hidden and position: absolute properties, but there's a nice demonstration during the video which shows you what they're doing.

#company_logo {
  background-color: #e3ede7;
  /* border: 1px solid orange; */
}

#company_logo img {
  max-width: 45%;

  /* top right bottom left */
  margin: 7px auto 5px 20px;
}

#slide_divs_container {
  position: relative;
  overflow: hidden;
  
  width: 100%;
  margin: 0 auto 0 auto;
  /* border: 5px solid orange; */
}

.slide_div {
  position: absolute;
  background-color: #2fb591;
}

.slide_top_text {
  text-align: center;  
  font-family: Calibri;

  padding: 8px 0 8px 0;
  color: #590000;
}

.controls_container {
  display: flex;
  justify-content: center;
  align-items: center;

  width: 100%;
  margin: 0 auto 0 auto;
  padding: 5px 0 5px 0;

  background-color: #afe9df;  
}

/* using class not id because id already used */
.play_banner, .pause_banner {
  display: flex;
  justify-content: center;
  align-items: center;

  cursor: pointer;

  font-size: 11px;
  font-family: Calibri;
  font-weight: bolder;

  margin-left: 15px;
  margin-right: 15px;
  padding: 3px 5px 3px 5px;

  color: black;
  background-color: #64e364;
  transition: background-color 0.6s ease;
}

.pause_banner {
  background-color: #de8381;
}

.play_banner:hover {
  color: black;
  background-color: #ddf9f2;
}

.pause_banner:hover {
  color: black;
  background-color: #f0d0d7;
}

.dot_button {
  cursor: pointer;
  width: 15px;
  height: 15px;
  margin: 0 4px 0 4px;
  
  border-radius: 50%;
  background-color: #c991a3;
  transition: background-color 0.75s ease;
}

.dot_button:hover {
  background-color: #ff9500;
}

.dot_button_active {
  background-color: #33c033;
}

.control_sliders {
  width: 100px;
  height: 13px;
}

#speed_indicator {
  margin-right: 10px;
  padding-top: 2px;
}

#duration_indicator {
  padding-top: 2px; 
  background: linear-gradient(125deg, #bb988c, #ffffff, #8cbb92);
}

#prev, #next {
  cursor: pointer;
  position: absolute;

  top: 50%; 
  padding: 10px;
  font-weight: bold;
  border-radius: 0 3px 3px 0;
  color: #ff3939;

  /* softens colour change on hover */
  transition: 0.6s ease;

  /* stops mouse being able to select text */
  user-select: none;

  /* places the arrows in front of the slide images */
  z-index: 3;
}

/* position the next button to the right */
#next {
  right: 0;
  border-radius: 3px 0 0 3px;
}

/* add transparent black background colour on hover */
#prev:hover, #next:hover {
  background-color: rgba(240, 231, 179, 0.55);
}

/* softens image change */
@keyframes image_fade {
  from {
    opacity: 0.2;
  }
  to {
    opacity: 1;
  }
}

.image_fade {  
  animation-name: image_fade;
  animation-duration: 0.5s;
}

@media only screen and (min-width: 1450px) {
  body {
    width: 70%;
    margin: auto;
  }
}