This HTML CSS & JavaScript code snippet implements a Scroll Based Animation on a webapge. It creates a custom scroll effect for a set of images within a container. The images move and rotate based on your scroll position, creating a visually engaging scrolling experience.
You can use this code on your website to enhance user engagement and make your content more interactive. It adds a dynamic scrolling animation to images, making your site visually appealing.
How to Create Scroll Based Animation In CSS
1. First of all, load the Normalize CSS by adding the following CDN link into the head tag of your HTML document.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
2. After that, create the HTML structure for your scroll-based animation. Place the images you want to animate within a container. Each image should be wrapped in a <div>
with the class “image.”
<!-- The `.container` element will contain all the images --> <!-- It will be used also to perform the 'custom scroll' behavior --> <div class="container"> <!-- Each following `div` correspond to one image --> <!-- The images will be set using CSS backgrounds --> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> <div class="image vh-fix"></div> </div>
3. Next, apply CSS styles to achieve the desired animation effect. The provided CSS code contains the necessary styles for this effect. It sets up the grid layout, image positioning, and animation properties.
body{ overflow-x: hidden; height: 100vh; background-color: #9f9eac; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1600 800'%3E%3Cg %3E%3Cpath fill='%23aeadbc' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/%3E%3Cpath fill='%23bdbccc' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/%3E%3Cpath fill='%23cdccdd' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/%3E%3Cpath fill='%23dcdbee' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/%3E%3Cpath fill='%23ecebff' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/%3E%3Cpath fill='%23dcdbee' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/%3E%3Cpath fill='%23cdccdd' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/%3E%3Cpath fill='%23bdbccc' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/%3E%3Cpath fill='%23aeadbc' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/%3E%3Cpath fill='%239f9eac' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/%3E%3C/g%3E%3C/svg%3E"); /* background by SVGBackgrounds.com */ background-attachment: fixed; background-position: center; background-size: cover; } .fake-scroll { position: absolute; top: 0; width: 1px; } .container { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 0 10%; justify-items: end; position: fixed; top: 0; left: 0; width: 100%; } .image { position: relative; width: 300px; height: 100vh; background-repeat: no-repeat; background-position: center; } .image:nth-child(2n) { justify-self: start; } .image:nth-child(1) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image1.jpg"); } .image:nth-child(2) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image2.jpg"); } .image:nth-child(3) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image3.jpg"); } .image:nth-child(4) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image4.jpg"); } .image:nth-child(5) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image5.jpg"); } .image:nth-child(6) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image6.jpg"); } .image:nth-child(7) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image7.jpg"); } .image:nth-child(8) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image8.jpg"); } .image:nth-child(9) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image9.jpg"); } .image:nth-child(10) { background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image10.jpg"); } @media screen and (max-width: 760px) { .container { grid-template-columns: 1fr; justify-items: center; } .image:nth-child(2n) { justify-self: center; } }
4. The following JavaScript code is responsible for creating the custom scroll effect. It adjusts the image positions and rotations based on the scroll position. You can use this code as is, but make sure to place it within a <script>
tag at the end of your HTML document, just before the closing </body>
tag.
/** * Regex tested and matched against the following userAgents: * iPhone * Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) * AppleWebKit/602.1.50 (KHTML, like Gecko) * CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1 * iPad * Mozilla/5.0 (iPad; CPU OS 9_0 like Mac OS X) * AppleWebKit/600.1.4 (KHTML, like Gecko) * CriOS/45.0.2454.89 Mobile/13A344 Safari/600.1.4 (000205) */ const iOSChromeDetected = /CriOS/.test(navigator.userAgent); if (iOSChromeDetected) { const getHeight = function getComputedHeightFrom(element) { const computedHeightString = getComputedStyle(element).height; const elementHeight = Number(computedHeightString.replace('px', '')); return elementHeight; }; const calculateVh = function calculateVhFrom(elementHeight) { const approximateVh = (elementHeight / initialViewportHeight) * 100; const elementVh = Math.round(approximateVh); return elementVh; }; const setDataAttribute = function setDataAttributeUsing(elementVh, element) { const dataAttributeValue = `${elementVh}`; element.setAttribute('data-vh', dataAttributeValue); }; const setHeight = function setHeightBasedOnVh(element) { const landscape = orientation; const vhRatio = Number(element.dataset.vh / 100); if (landscape) { element.style.height = `${vhRatio * landscapeHeight}px`; } else { element.style.height = `${vhRatio * portraitHeight}px`; } }; const initialize = function initializeDataAttributeAndHeight(element) { const elementHeight = getHeight(element); const elementVh = calculateVh(elementHeight); setDataAttribute(elementVh, element); setHeight(element); }; const initialViewportHeight = window.innerHeight; const elements = Array.from(document.getElementsByClassName('vh-fix')); const statusBarHeight = 20; const portraitHeight = screen.height - statusBarHeight; const landscapeHeight = screen.width - statusBarHeight; window.onload = function() { window.addEventListener('orientationchange', function() { elements.forEach(setHeight); }); elements.forEach(initialize); }; } // DEMO (function() { // Easing function used for `translateX` animation // From: https://gist.github.com/gre/1650294 function easeOutQuad (t) { return t * (2 - t) } // Returns a random number (integer) between `min` and `max` function random (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } // Returns a random number as well, but it could be negative also function randomPositiveOrNegative (min, max) { return random(min, max) * (Math.random() > 0.5 ? 1 : -1) } // Set CSS `tranform` property for an element function setTransform (el, transform) { el.style.transform = transform el.style.WebkitTransform = transform } // Current scroll position var current = 0 // Target scroll position var target = 0 // Ease or speed for moving from `current` to `target` var ease = 0.075 // Utility variables for `requestAnimationFrame` var rafId = undefined var rafActive = false // Container element var container = document.querySelector('.container') // Array with `.image` elements var images = Array.prototype.slice.call(document.querySelectorAll('.image')) // Variables for storing dimmensions var windowWidth, containerHeight, imageHeight // Variables for specifying transform parameters and limits var rotateXMaxList = [] var rotateYMaxList = [] var translateXMax = -200 // Popullating the `rotateXMaxList` and `rotateYMaxList` with random values images.forEach(function () { rotateXMaxList.push(randomPositiveOrNegative(20, 40)) rotateYMaxList.push(randomPositiveOrNegative(20, 60)) }) // The `fakeScroll` is an element to make the page scrollable // Here we are creating it and appending it to the `body` var fakeScroll = document.createElement('div') fakeScroll.className = 'fake-scroll' document.body.appendChild(fakeScroll) // In the `setupAnimation` function (below) we will set the `height` properly // Geeting dimmensions and setting up all for animation function setupAnimation () { // Updating dimmensions windowWidth = window.innerWidth containerHeight = container.getBoundingClientRect().height imageHeight = containerHeight / (windowWidth > 760 ? images.length / 2 : images.length) // Set `height` for the fake scroll element fakeScroll.style.height = containerHeight + 'px' // Start the animation, if it is not running already startAnimation() } // Update scroll `target`, and start the animation if it is not running already function updateScroll () { target = window.scrollY || window.pageYOffset startAnimation() } // Start the animation, if it is not running already function startAnimation () { if (!rafActive) { rafActive = true rafId = requestAnimationFrame(updateAnimation) } } // Do calculations and apply CSS `transform`s accordingly function updateAnimation () { // Difference between `target` and `current` scroll position var diff = target - current // `delta` is the value for adding to the `current` scroll position // If `diff < 0.1`, make `delta = 0`, so the animation would not be endless var delta = Math.abs(diff) < 0.1 ? 0 : diff * ease if (delta) { // If `delta !== 0` // Update `current` scroll position current += delta // Round value for better performance current = parseFloat(current.toFixed(2)) // Call `update` again, using `requestAnimationFrame` rafId = requestAnimationFrame(updateAnimation) } else { // If `delta === 0` // Update `current`, and finish the animation loop current = target rafActive = false cancelAnimationFrame(rafId) } // Update images updateAnimationImages() // Set the CSS `transform` corresponding to the custom scroll effect setTransform(container, 'translateY('+ -current +'px)') } // Calculate the CSS `transform` values for each `image`, given the `current` scroll position function updateAnimationImages () { // This value is the `ratio` between `current` scroll position and image's `height` var ratio = current / imageHeight // Some variables for using in the loop var intersectionRatioIndex, intersectionRatioValue, intersectionRatio var rotateX, rotateXMax, rotateY, rotateYMax, translateX // For each `image` element, make calculations and set CSS `transform` accordingly images.forEach(function (image, index) { // Calculating the `intersectionRatio`, similar to the value provided by // the IntersectionObserver API intersectionRatioIndex = windowWidth > 760 ? parseInt(index / 2) : index intersectionRatioValue = ratio - intersectionRatioIndex intersectionRatio = Math.max(0, 1 - Math.abs(intersectionRatioValue)) // Calculate the `rotateX` value for the current `image` rotateXMax = rotateXMaxList[index] rotateX = rotateXMax - (rotateXMax * intersectionRatio) rotateX = rotateX.toFixed(2) // Calculate the `rotateY` value for the current `image` rotateYMax = rotateYMaxList[index] rotateY = rotateYMax - (rotateYMax * intersectionRatio) rotateY = rotateY.toFixed(2) // Calculate the `translateX` value for the current `image` if (windowWidth > 760) { translateX = translateXMax - (translateXMax * easeOutQuad(intersectionRatio)) translateX = translateX.toFixed(2) } else { translateX = 0 } // Invert `rotateX` and `rotateY` values in case the image is below the center of the viewport // Also update `translateX` value, to achieve an alternating effect if (intersectionRatioValue < 0) { rotateX = -rotateX rotateY = -rotateY translateX = index % 2 ? -translateX : 0 } else { translateX = index % 2 ? 0 : translateX } // Set the CSS `transform`, using calculated values setTransform(image, 'perspective(500px) translateX('+ translateX +'px) rotateX('+ rotateX +'deg) rotateY('+ rotateY +'deg)') }) } // Listen for `resize` event to recalculate dimmensions window.addEventListener('resize', setupAnimation) // Listen for `scroll` event to update `target` scroll position window.addEventListener('scroll', updateScroll) // Initial setup setupAnimation() })()
That’s all! hopefully, you have successfully created a Scroll Based animation in HTML, CSS, and JavaScript. If you have any questions or suggestions, feel free to comment below.
Similar Code Snippets:
I code and create web elements for amazing people around the world. I like work with new people. New people new Experiences.
I truly enjoy what I’m doing, which makes me more passionate about web development and coding. I am always ready to do challenging tasks whether it is about creating a custom CMS from scratch or customizing an existing system.