Materials for Storytelling On Scroll + Slider

Free images:

Code:

1. CSS classes and ID:

				
					fullpage

moon

section1
section

section2
section

section3
section

section4
section


navbar
logo
logo
				
			

2. Menu:

				
					Sci-Fi Open World <span id="dynamic-word">Game</span>
				
			

3. CSS code:

				
					 <link data-minify="1"
      rel="stylesheet"
      href="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/fullPage.js/3.1.2/fullpage.min.css?ver=1764865062"
    />
<style>
    
      .section {
        display: flex;
        flex-direction: column;
        justify-content: bottom;
        align-items: bottom;
        height: 100vh;
        text-align: bottom;
        font-size: 6rem;
        overflow: hidden;
        position: relative;
        /*transition: transform 5s ease-in-out;*/
      }

      /* Navbar */
      .navbar {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        background-color: transparent;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 10px 20px;
        z-index: 10;
      }

      .navbar .logo {
        font-size: 1.5rem;
        font-weight: bold;
        color: #f1f1f1;
      }

      .navbar .links {
        display: flex;
        white-space: nowrap;
      }
      .navbar .links a{
          text-decoration: none;
      }

      /* Change main text color to black */
      h1 {
        color: black;
        opacity: 1;
        margin-bottom: 20px;
      }

     

      /* Scroll text styling */
      .scroll-text {
        position: fixed;
        top: 50%;
        transform: translateY(-50%) rotate(-90deg);
        font-size: 1.5rem;
        color: #f1f1f1;
        opacity: 0.8;
        z-index: 5;
      }

      .scroll-text.left {
        left: 20px;
      }

      .scroll-text.right {
        right: 20px;
      }


.fp-tableCell {
    display: table-cell;
    vertical-align: center;
    width: 100%;
    height: 100%;

 </style>



<script data-minify="1" src="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/gsap/3.11.5/gsap.min.js?ver=1759923598"></script>
<script data-minify="1" src="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js?ver=1759923598"></script> 


				
			

4. Javascript:

				
					<script data-minify="1"
  src="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/fullPage.js/3.1.2/fullpage.min.js?ver=1759923533"
></script>
<script data-minify="1"
  src="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/gsap/3.11.5/gsap.min.js?ver=1759923598"
></script>
<script data-minify="1"
  src="https://nicolaipalmkvist.com/wp-content/cache/min/1/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js?ver=1759923598"
></script>
<script>
  document.addEventListener("DOMContentLoaded", function () {
    const fullpageInstance = new fullpage(".fullpage", {
      scrollingSpeed: 1000,
      autoScrolling: true,
      scrollHorizontally: true,
      navigation: false,
      onLeave: function (origin, destination, direction) {
        const words = ["Game", "Journey", "Odyssey", "Adventure"];
        const leavingText = origin.item.querySelector("h1");
        const enteringText = destination.item.querySelector("h1");
        const line = destination.item.querySelector(".line");
        const dynamicWord = document.getElementById("dynamic-word");
        const navbarText = document.querySelectorAll(".navbar a, .navbar h2");
        const scrollTexts = document.querySelectorAll(".scroll-text h2");

        gsap.to(leavingText, {
          opacity: 0,
          y: -100,
          duration: 1.5,
          ease: "power2.out",
        });

        // Animate the entering text to come in from below
        gsap.fromTo(
          enteringText,
          { y: 100, opacity: 0 },
          {
            y: 0,
            opacity: 1,
            color: destination.item.getAttribute("data-text-color"), // Set text color
            duration: 1.5,
            ease: "power2.out",
            delay: 0.5,
          }
        );

        // Set the dynamic word
        dynamicWord.textContent = words[destination.index];

        // Get the destination colors
        const newBgColor = destination.item.getAttribute("data-bg");
        const newTextColor = destination.item.getAttribute("data-text-color");

        // Change background color of destination slide
        destination.item.style.backgroundColor = newBgColor;

        // Change text colors dynamically
        gsap.to(dynamicWord, { color: newTextColor, duration: 0.5 });
        gsap.to(line, { color: newTextColor, duration: 0.5 });
        gsap.to(navbarText, { color: newTextColor, duration: 0.5 });
        gsap.to(scrollTexts, { color: newTextColor, duration: 0.5 });

        // Line growth/shrinkage animation
        const newLineWidth = (destination.index + 1) * 150;
        const currentLineWidth = (origin.index + 1) * 150;

        if (direction === "down") {
          gsap.to(line, {
            width: newLineWidth + "px",
            duration: 1.5,
            ease: "power2.out",
          });
        } else {
          gsap.to(line, {
            width: currentLineWidth + "px",
            duration: 1.5,
            ease: "power2.out",
          });
        }
      },
    });

    // Use querySelectorAll to select elements by class
    const links = document.querySelectorAll(".link1, .link2, .link3, .link4");

    links.forEach(function (link) {
      link.addEventListener("click", function () {
        const sectionIndex = parseInt(link.classList[0].replace('link', '')); // Get section index from class
        scrollToSection(sectionIndex);
      });
    });

    function scrollToSection(sectionIndex) {
      fullpageInstance.moveTo(sectionIndex);
    }
  });
</script>

				
			

5. Let make it move:

Moon:

				
					 .moon {
    transform-origin: center center;
    animation: kenBurns 10s ease-in-out infinite alternate;
}

@keyframes kenBurns {
    0% {
        transform: scale(1);
    }
    100% {
        transform: scale(1.2);
    }
}


				
			

Hero sections:

				
					 #section1 {
    transform-origin: center center;
    animation: kenBurns 10s ease-in-out infinite alternate;
}

@keyframes kenBurns {
    0% {
        transform: scale(1);
    }
    100% {
        transform: scale(1.2);
    }
}




				
			

NextGen Elementor Slider

Free images:

Code:

1. CSS class:

				
					as-slider

as-bar

as-changing-widget

as-side-slider

as-slider-left

as-slider-right

as-changing-widget

				
			

2. Javascript code in right side

				
					<script data-minify="1" src="https://nicolaipalmkvist.com/wp-content/cache/min/1/jquery-3.6.0.min.js?ver=1759923617"></script>

<script>

var $ = jQuery
    
$(document).ready(function(){

$('.as-slider').each(function(){

var $this = $(this),
    currentSlide = 0,
    previousSlide = 0,
    slideNumber = $this.find('.as-side-slider .swiper-slide:not(.swiper-slide-duplicate)').length,
    barHTML = '',
    forward,
    textContainer = $this.find('.as-changing-widget')
   
for(var i=0; i<slideNumber;i++){

    barHTML += `<span class="dot"><span class="dot-number">${i+1}</span></span>`
}

$this.find('.as-bar .dot').remove()
$this.find('.as-bar').append(barHTML)
$this.find('.as-bar .dot').eq(0).addClass('active')
    
textContainer.each(function(){
    var texts = $(this).find('.elementor-widget').eq(0)
    texts.addClass('currentUp')
    $(this).css('--h', texts.height()+'px')
})

setTimeout(function(){
    $this.addClass('loaded')
    if($this.find('.as-side-slider .swiper-container-initialized, .as-side-slider .swiper-initialized').length){
        $this.find('.as-side-slider').addClass('loaded')
    }

    var init = setInterval(function(){
        if($this.find('.as-side-slider .swiper-container-initialized, .as-side-slider .swiper-initialized').length){

            $this.find('.as-side-slider').addClass('loaded')
            clearInterval(init)
        }
    },50)
}, 500)

var bgs = JSON.parse($this.attr('data-settings')).background_slideshow_gallery,
    bgHTML = '<div class="as-slider-background">'

if(bgs){
    bgs.forEach(function(background){
        bgHTML += `<img decoding="async" src="${background.url}"/>`
    })
}
bgHTML += '</div>'

$this.find('.as-slider-background').remove()
$this.prepend(bgHTML)

var backgrounds = $this.find('.as-slider-background img')

backgrounds.eq(0).addClass('currentForward')

setInterval(function(){
    currentSlide = $this.find('.as-side-slider .swiper-slide-active').attr('data-swiper-slide-index')
    if(previousSlide != currentSlide) {

        if( previousSlide < currentSlide ){
            forward = true
        }
        if( previousSlide > currentSlide ){
            forward = false
        }
        if( previousSlide == slideNumber - 1 && currentSlide == 0 ){
            forward = true
        }
        if( previousSlide == 0 && currentSlide == slideNumber - 1 ){
            forward = false
        }
        textContainer.each(function(){
            var texts = $(this).find('.elementor-widget')
            
            $(this).css('--h', texts.eq(currentSlide).height()+'px')
            
            texts.removeClass('prev next currentUp currentDown')
            backgrounds.removeClass('prev currentBackward currentForward')
            
            backgrounds.eq(previousSlide).addClass('prev')
            
            if(forward) {
                texts.eq(previousSlide).addClass('prev')
                texts.eq(currentSlide).addClass('currentUp')
                
                backgrounds.eq(currentSlide).addClass('currentForward')
                
            }else{
                texts.eq(previousSlide).addClass('next')
                texts.eq(currentSlide).addClass('currentDown')

                backgrounds.eq(currentSlide).addClass('currentBackward')
            }
        })
        
        $this.find('.as-bar .dot').removeClass('active')
        $this.find('.as-bar .dot').eq(currentSlide).addClass('active')
    }
    previousSlide = currentSlide
}, 500)

$this.find('.as-bar .dot').on('click', function(){
    
    var index = $(this).index()
    
    $this.find('.as-side-slider .swiper-pagination-bullet').eq(index).trigger('click')
    $this.find('.as-side-slider .swiper-container').trigger('mouseleave')
    
})
$this.find('.as-slider-left').on('click', function(){
    
    $this.find('.as-side-slider .elementor-swiper-button-prev').trigger('click')
    $this.find('.as-side-slider .elementor-swiper').trigger('mouseleave')
})
$this.find('.as-slider-right').on('click', function(){
    
    $this.find('.as-side-slider .elementor-swiper-button-next').trigger('click')
    $this.find('.as-side-slider .elementor-swiper').trigger('mouseleave')
})
$this.find('.as-slider-left a, .as-slider-right a').on('click', function(e){
    
    e.preventDefault()
})

})
})

$(window).on('resize', function(){
    
    
$('.as-slider').each(function(){
    
    var textContainer = $(this).find('.as-changing-widget')
    
    textContainer.each(function(){
        var texts = $(this).find('.elementor-widget.currentUp, .elementor-widget.currentDown')
    
        $(this).css('--h', texts.height()+'px')
    })
})
})

</script>
				
			

3. CSS code in navigation container:

				
					selector{
    --dot-size: 23px;
    --line-color: #D8D9D8;
    --dot-color: #D8D9D8;
    --dot-color-active: #D8D9D8;
    color: #2A2F2F;
    font-size: 13px;
    font-weight: bold;
}
selector{
    height: 80vh;
    height: var(--min-height);
    max-height: 80vh;
    min-height: 0 !important;
}
selector .dot{
    height: var(--dot-size);
    width: var(--dot-size);
    background: var(--dot-color);
    border-radius: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    transform: scale(0.3);
    transition: all 0.3s ease-in-out;
    cursor: pointer;
}
selector .dot-number{
    opacity: 0;
    transition: all 0.3s ease-in-out;
}
selector .dot.active{
    transform: scale(1);
    background: var(--dot-color-active);
}
selector .dot.active .dot-number{
    opacity: 1;
}
selector:before{
    content: "";
    position: absolute;
    top: 50%;
    height: calc(100% - 20px);
    max-height: 90vh;
    width: 1px;
    background: var(--line-color);
    left: 50%;
    transform: translateX(-50%) translateY(-50%);
}

@media (max-width: 767px){
selector{
    transform: translateX(-50%);
    flex-wrap: nowrap !important;
}
selector:before {
    width: calc(100% - 20px);
    height: 1px;

}
}
				
			

4. CSS code in testimonial carousel

				
					selector{
    --radius: 8px;
    --height: 320px;
    --active-height: 360px;
    --overlay: 0.75;
}
selector{
    opacity: 0;
    transform: translateX(100px);
    transition: all 0.8s ease-in-out;
}
selector.loaded{
    opacity: 1;
    transform: translateX(0);
}

selector .swiper-wrapper{
    height: var(--active-height);
    align-items: center;
}
selector:not(.loaded) .swiper-wrapper{
    transition-duration: 0s !important;
}
selector .swiper-slide{
    display: flex;
    align-items: flex-end;
    border-radius: var(--radius);
    height: var(--height);
    box-shadow: 0 0 50px rgba(0,0,0,0.15);
}
selector.loaded .swiper-slide{
    transition: all 0.3s ease-in-out 0.2s;
}
selector .swiper-slide.swiper-slide-active{
    height: var(--active-height);
}
selector .swiper-slide:before{
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    background: rgb(0,0,0);
    background: linear-gradient(20deg, rgba(0,0,0,var(--overlay)) 0%, rgba(0,0,0,0) 100%);
    height: 100%;
    width: 100%;
    z-index: 1;
}
selector .elementor-testimonial__footer{
    display: block;
}
selector img{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border-radius: var(--radius);
}
selector .elementor-testimonial__cite{
    z-index: 2;
    position: relative;
}
selector .elementor-testimonial__name{
    margin-bottom: 5px;
}
selector .swiper-pagination,
selector .elementor-swiper-button{
    display: none;
}
selector .swiper-container{
    overflow: hidden;
    margin-left: auto;
    margin-right: auto;
}

@media (max-width: 1024px){
selector{
    --height: 180px;
    --active-height: 250px;
}
}
@media (max-width: 767px){
selector{
    --height: 120px;
    --active-height: 140px;
    width: 100% !important;
    max-width: var(--container-widget-width, 300px) !important;
}
selector .elementor-testimonial__cite{
    opacity: 0;
}
}
				
			

5. CSS code in main container:

				
					selector{
background: linear-gradient(90deg, #b827b8, #4c0d4c);
   --background-speed: 0.5s;
}
selector .elementor-background-slideshow{
    display: none;
}
selector .as-slider-background,
selector .as-slider-background img{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transition: all 1s ease-in-out;
}
selector .as-slider-background::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 40%;
    height: 100%;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0)); /* soft dark to transparent */
    z-index: 2;
    pointer-events: none;
}

selector .as-slider-background img{
    width: 40%;
    object-fit: cover;
    opacity: 0;
    transform: scale(1.1);
}
selector .as-slider-background img.prev,
selector .as-slider-background img.currentBackward,
selector .as-slider-background img.currentForward{
    opacity: 1;
    transform: scale(1.1);
}

selector .as-slider-background img.currentBackward,
selector .as-slider-background img.currentForward{
    z-index: 1;
    opacity: 1;
    animation: bgNext var(--background-speed) linear;
    transition: all 1s ease-in-out;
    transform: scale(1);
}

selector:before{
    z-index: 2;
}
selector > .elementor-element{
    z-index: 3;
}

selector .as-bar,
selector .as-slider-left,
selector .as-slider-right{
    opacity: 0;
    transition: all 0.8s ease-in-out;
}
selector.loaded .as-bar,
selector.loaded .as-slider-left,
selector.loaded .as-slider-right{
    opacity: 1;
}
/*selector .ds-slider-left a:focus,*/
/*selector .ds-slider-right a:focus{*/
/*    outline: none !important;*/
/*}*/

@keyframes bgNext {
  0%   {opacity: 0; transform: scale(1.1);}
  100%   {opacity: 1; transform: scale(1);}
}

@media (min-width: 768px){
selector .as-bar,
selector .as-slider-left,
selector .as-slider-right{
    position: relative;
}
}

@media (max-width: 1380px) and (min-width: 768px){
selector{
    padding-left: 4%;
    padding-right: 4%;
}
}

@media (max-width: 1024px){
selector .as-slider-background::before {
    width: 60%;
}

selector .as-slider-background img{
    width: 60%;
}    
}

@media (max-width: 767px){
selector .as-slider-left{
    left: calc(50% - 300px/2) !important;
}
selector .as-slider-right{
    right: calc(50% - 300px/2) !important;
}
selector .as-slider-background::before {
    width: 100%;
}

selector .as-slider-background img{
    width: 100%;
}

}
				
			

6. CSS code in heading container

				
					selector{
    --speed: 0.6s;
    --gap: 40px;
}
selector{
    transition: all 0.3s ease-in-out;
    height: var(--h);
    --height: calc(var(--h) + var(--gap));
    overflow: hidden !important;
}
selector .elementor-widget{
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
}
selector .elementor-widget > *{
    transform: translateY(calc(-10 * var(--height)));
    transition: none !important;
}
selector .elementor-widget.prev > *{
    animation: prev var(--speed) ease-in-out;
    transform: translateY(calc(-1 * var(--height)));
}
selector .elementor-widget.next > *{
    animation: next var(--speed) ease-in-out;
    transform: translateY(var(--height));
}
selector .elementor-widget.currentUp,
selector .elementor-widget.currentDown{
    z-index: 1;
}
selector .elementor-widget.currentUp > *{
    animation: currentUp var(--speed) ease-in-out;
    transform: translateY(0);
}
selector .elementor-widget.currentDown > *{
    animation: currentDown var(--speed) ease-in-out;
    transform: translateY(0);
}

@keyframes prev {
  0%   {transform: translateY(0);}
  100%   {transform: translateY(calc(-1 * var(--height)));}
}

@keyframes next {
  0%   {transform: translateY(0);}
  100%   {transform: translateY(var(--height));}
}

@keyframes currentUp {
  0%   {transform: translateY(var(--height));}
  100%   {transform: translateY(0);}
}

@keyframes currentDown {
  0%   {transform: translateY(calc(-1 * var(--height)));}
  100%   {transform: translateY(0);}
}
				
			

7. CSS code in sub heading area:

				
					selector{
    --speed: 0.8s;
    --gap: 40px;
}
selector{
    transition: all 0.3s ease-in-out;
    height: var(--h);
    --height: calc(var(--h) + var(--gap));
    overflow: hidden !important;
}
selector .elementor-widget{
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
}
selector .elementor-widget > *{
    transform: translateY(calc(-10 * var(--height)));
    transition: none !important;
}
selector .elementor-widget.prev > *{
    animation: prev var(--speed) ease-in-out;
    transform: translateY(calc(-1 * var(--height)));
}
selector .elementor-widget.next > *{
    animation: next var(--speed) ease-in-out;
    transform: translateY(var(--height));
}
selector .elementor-widget.currentUp,
selector .elementor-widget.currentDown{
    z-index: 1;
}
selector .elementor-widget.currentUp > *{
    animation: currentUp var(--speed) ease-in-out;
    transform: translateY(0);
}
selector .elementor-widget.currentDown > *{
    animation: currentDown var(--speed) ease-in-out;
    transform: translateY(0);
}

@keyframes prev {
  0%   {transform: translateY(0);}
  100%   {transform: translateY(calc(-1 * var(--height)));}
}

@keyframes next {
  0%   {transform: translateY(0);}
  100%   {transform: translateY(var(--height));}
}

@keyframes currentUp {
  0%   {transform: translateY(var(--height));}
  100%   {transform: translateY(0);}
}

@keyframes currentDown {
  0%   {transform: translateY(calc(-1 * var(--height)));}
  100%   {transform: translateY(0);}
}
				
			

Download free images from project

Fill out the form to get the 6 images I used in my YouTube video.

By signing up, you’ll also join my newsletter – full of free web design tips and resources.
No spam, unsubscribe with one-click.