Scroll Based Animation in CSS

Scroll Based Animation in CSS
Code Snippet:Scroll Based Animation
Author: lmgonzalves
Published: January 21, 2024
Last Updated: January 22, 2024
Downloads: 1,048
License: MIT
Edit Code online: View on CodePen
Read More

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.

  1. <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.”

  1. <!-- The `.container` element will contain all the images -->
  2. <!-- It will be used also to perform the 'custom scroll' behavior -->
  3. <div class="container">
  4. <!-- Each following `div` correspond to one image -->
  5. <!-- The images will be set using CSS backgrounds -->
  6. <div class="image vh-fix"></div>
  7. <div class="image vh-fix"></div>
  8. <div class="image vh-fix"></div>
  9. <div class="image vh-fix"></div>
  10. <div class="image vh-fix"></div>
  11. <div class="image vh-fix"></div>
  12. <div class="image vh-fix"></div>
  13. <div class="image vh-fix"></div>
  14. <div class="image vh-fix"></div>
  15. <div class="image vh-fix"></div>
  16. </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.

  1. body{
  2. overflow-x: hidden;
  3. height: 100vh;
  4. background-color: #9f9eac;
  5. 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");
  6. /* background by SVGBackgrounds.com */
  7. background-attachment: fixed;
  8. background-position: center;
  9. background-size: cover;
  10. }
  11.  
  12. .fake-scroll {
  13. position: absolute;
  14. top: 0;
  15. width: 1px;
  16. }
  17.  
  18. .container {
  19. display: grid;
  20. grid-template-columns: 1fr 1fr;
  21. grid-gap: 0 10%;
  22. justify-items: end;
  23. position: fixed;
  24. top: 0;
  25. left: 0;
  26. width: 100%;
  27. }
  28.  
  29. .image {
  30. position: relative;
  31. width: 300px;
  32. height: 100vh;
  33. background-repeat: no-repeat;
  34. background-position: center;
  35. }
  36. .image:nth-child(2n) {
  37. justify-self: start;
  38. }
  39. .image:nth-child(1) {
  40. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image1.jpg");
  41. }
  42. .image:nth-child(2) {
  43. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image2.jpg");
  44. }
  45. .image:nth-child(3) {
  46. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image3.jpg");
  47. }
  48. .image:nth-child(4) {
  49. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image4.jpg");
  50. }
  51. .image:nth-child(5) {
  52. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image5.jpg");
  53. }
  54. .image:nth-child(6) {
  55. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image6.jpg");
  56. }
  57. .image:nth-child(7) {
  58. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image7.jpg");
  59. }
  60. .image:nth-child(8) {
  61. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image8.jpg");
  62. }
  63. .image:nth-child(9) {
  64. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image9.jpg");
  65. }
  66. .image:nth-child(10) {
  67. background-image: url("https://cdn.jsdelivr.net/gh/lmgonzalves/scroll-based-animation/img/image10.jpg");
  68. }
  69.  
  70. @media screen and (max-width: 760px) {
  71. .container {
  72. grid-template-columns: 1fr;
  73. justify-items: center;
  74. }
  75.  
  76. .image:nth-child(2n) {
  77. justify-self: center;
  78. }
  79. }

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.

  1. /**
  2. * Regex tested and matched against the following userAgents:
  3. * iPhone
  4. * Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X)
  5. * AppleWebKit/602.1.50 (KHTML, like Gecko)
  6. * CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1
  7. * iPad
  8. * Mozilla/5.0 (iPad; CPU OS 9_0 like Mac OS X)
  9. * AppleWebKit/600.1.4 (KHTML, like Gecko)
  10. * CriOS/45.0.2454.89 Mobile/13A344 Safari/600.1.4 (000205)
  11. */
  12. const iOSChromeDetected = /CriOS/.test(navigator.userAgent);
  13.  
  14. if (iOSChromeDetected) {
  15. const getHeight = function getComputedHeightFrom(element) {
  16. const computedHeightString = getComputedStyle(element).height;
  17. const elementHeight = Number(computedHeightString.replace('px', ''));
  18. return elementHeight;
  19. };
  20.  
  21. const calculateVh = function calculateVhFrom(elementHeight) {
  22. const approximateVh = (elementHeight / initialViewportHeight) * 100;
  23. const elementVh = Math.round(approximateVh);
  24. return elementVh;
  25. };
  26.  
  27. const setDataAttribute = function setDataAttributeUsing(elementVh, element) {
  28. const dataAttributeValue = `${elementVh}`;
  29. element.setAttribute('data-vh', dataAttributeValue);
  30. };
  31.  
  32. const setHeight = function setHeightBasedOnVh(element) {
  33. const landscape = orientation;
  34. const vhRatio = Number(element.dataset.vh / 100);
  35. if (landscape) {
  36. element.style.height = `${vhRatio * landscapeHeight}px`;
  37. } else {
  38. element.style.height = `${vhRatio * portraitHeight}px`;
  39. }
  40. };
  41.  
  42. const initialize = function initializeDataAttributeAndHeight(element) {
  43. const elementHeight = getHeight(element);
  44. const elementVh = calculateVh(elementHeight);
  45. setDataAttribute(elementVh, element);
  46. setHeight(element);
  47. };
  48.  
  49. const initialViewportHeight = window.innerHeight;
  50. const elements = Array.from(document.getElementsByClassName('vh-fix'));
  51. const statusBarHeight = 20;
  52. const portraitHeight = screen.height - statusBarHeight;
  53. const landscapeHeight = screen.width - statusBarHeight;
  54.  
  55. window.onload = function() {
  56. window.addEventListener('orientationchange', function() {
  57. elements.forEach(setHeight);
  58. });
  59.  
  60. elements.forEach(initialize);
  61. };
  62. }
  63.  
  64.  
  65.  
  66. // DEMO
  67.  
  68. (function() {
  69.  
  70. // Easing function used for `translateX` animation
  71. // From: https://gist.github.com/gre/1650294
  72. function easeOutQuad (t) {
  73. return t * (2 - t)
  74. }
  75.  
  76. // Returns a random number (integer) between `min` and `max`
  77. function random (min, max) {
  78. return Math.floor(Math.random() * (max - min + 1)) + min
  79. }
  80.  
  81. // Returns a random number as well, but it could be negative also
  82. function randomPositiveOrNegative (min, max) {
  83. return random(min, max) * (Math.random() > 0.5 ? 1 : -1)
  84. }
  85.  
  86. // Set CSS `tranform` property for an element
  87. function setTransform (el, transform) {
  88. el.style.transform = transform
  89. el.style.WebkitTransform = transform
  90. }
  91.  
  92. // Current scroll position
  93. var current = 0
  94. // Target scroll position
  95. var target = 0
  96. // Ease or speed for moving from `current` to `target`
  97. var ease = 0.075
  98. // Utility variables for `requestAnimationFrame`
  99. var rafId = undefined
  100. var rafActive = false
  101. // Container element
  102. var container = document.querySelector('.container')
  103. // Array with `.image` elements
  104. var images = Array.prototype.slice.call(document.querySelectorAll('.image'))
  105. // Variables for storing dimmensions
  106. var windowWidth, containerHeight, imageHeight
  107.  
  108. // Variables for specifying transform parameters and limits
  109. var rotateXMaxList = []
  110. var rotateYMaxList = []
  111. var translateXMax = -200
  112.  
  113. // Popullating the `rotateXMaxList` and `rotateYMaxList` with random values
  114. images.forEach(function () {
  115. rotateXMaxList.push(randomPositiveOrNegative(20, 40))
  116. rotateYMaxList.push(randomPositiveOrNegative(20, 60))
  117. })
  118.  
  119. // The `fakeScroll` is an element to make the page scrollable
  120. // Here we are creating it and appending it to the `body`
  121. var fakeScroll = document.createElement('div')
  122. fakeScroll.className = 'fake-scroll'
  123. document.body.appendChild(fakeScroll)
  124. // In the `setupAnimation` function (below) we will set the `height` properly
  125.  
  126. // Geeting dimmensions and setting up all for animation
  127. function setupAnimation () {
  128. // Updating dimmensions
  129. windowWidth = window.innerWidth
  130. containerHeight = container.getBoundingClientRect().height
  131. imageHeight = containerHeight / (windowWidth > 760 ? images.length / 2 : images.length)
  132. // Set `height` for the fake scroll element
  133. fakeScroll.style.height = containerHeight + 'px'
  134. // Start the animation, if it is not running already
  135. startAnimation()
  136. }
  137.  
  138. // Update scroll `target`, and start the animation if it is not running already
  139. function updateScroll () {
  140. target = window.scrollY || window.pageYOffset
  141. startAnimation()
  142. }
  143.  
  144. // Start the animation, if it is not running already
  145. function startAnimation () {
  146. if (!rafActive) {
  147. rafActive = true
  148. rafId = requestAnimationFrame(updateAnimation)
  149. }
  150. }
  151.  
  152. // Do calculations and apply CSS `transform`s accordingly
  153. function updateAnimation () {
  154. // Difference between `target` and `current` scroll position
  155. var diff = target - current
  156. // `delta` is the value for adding to the `current` scroll position
  157. // If `diff < 0.1`, make `delta = 0`, so the animation would not be endless
  158. var delta = Math.abs(diff) < 0.1 ? 0 : diff * ease
  159.  
  160. if (delta) { // If `delta !== 0`
  161. // Update `current` scroll position
  162. current += delta
  163. // Round value for better performance
  164. current = parseFloat(current.toFixed(2))
  165. // Call `update` again, using `requestAnimationFrame`
  166. rafId = requestAnimationFrame(updateAnimation)
  167. } else { // If `delta === 0`
  168. // Update `current`, and finish the animation loop
  169. current = target
  170. rafActive = false
  171. cancelAnimationFrame(rafId)
  172. }
  173.  
  174. // Update images
  175. updateAnimationImages()
  176.  
  177. // Set the CSS `transform` corresponding to the custom scroll effect
  178. setTransform(container, 'translateY('+ -current +'px)')
  179. }
  180.  
  181. // Calculate the CSS `transform` values for each `image`, given the `current` scroll position
  182. function updateAnimationImages () {
  183. // This value is the `ratio` between `current` scroll position and image's `height`
  184. var ratio = current / imageHeight
  185. // Some variables for using in the loop
  186. var intersectionRatioIndex, intersectionRatioValue, intersectionRatio
  187. var rotateX, rotateXMax, rotateY, rotateYMax, translateX
  188.  
  189. // For each `image` element, make calculations and set CSS `transform` accordingly
  190. images.forEach(function (image, index) {
  191. // Calculating the `intersectionRatio`, similar to the value provided by
  192. // the IntersectionObserver API
  193. intersectionRatioIndex = windowWidth > 760 ? parseInt(index / 2) : index
  194. intersectionRatioValue = ratio - intersectionRatioIndex
  195. intersectionRatio = Math.max(0, 1 - Math.abs(intersectionRatioValue))
  196. // Calculate the `rotateX` value for the current `image`
  197. rotateXMax = rotateXMaxList[index]
  198. rotateX = rotateXMax - (rotateXMax * intersectionRatio)
  199. rotateX = rotateX.toFixed(2)
  200. // Calculate the `rotateY` value for the current `image`
  201. rotateYMax = rotateYMaxList[index]
  202. rotateY = rotateYMax - (rotateYMax * intersectionRatio)
  203. rotateY = rotateY.toFixed(2)
  204. // Calculate the `translateX` value for the current `image`
  205. if (windowWidth > 760) {
  206. translateX = translateXMax - (translateXMax * easeOutQuad(intersectionRatio))
  207. translateX = translateX.toFixed(2)
  208. } else {
  209. translateX = 0
  210. }
  211. // Invert `rotateX` and `rotateY` values in case the image is below the center of the viewport
  212. // Also update `translateX` value, to achieve an alternating effect
  213. if (intersectionRatioValue < 0) {
  214. rotateX = -rotateX
  215. rotateY = -rotateY
  216. translateX = index % 2 ? -translateX : 0
  217. } else {
  218. translateX = index % 2 ? 0 : translateX
  219. }
  220. // Set the CSS `transform`, using calculated values
  221. setTransform(image, 'perspective(500px) translateX('+ translateX +'px) rotateX('+ rotateX +'deg) rotateY('+ rotateY +'deg)')
  222. })
  223. }
  224.  
  225. // Listen for `resize` event to recalculate dimmensions
  226. window.addEventListener('resize', setupAnimation)
  227. // Listen for `scroll` event to update `target` scroll position
  228. window.addEventListener('scroll', updateScroll)
  229.  
  230. // Initial setup
  231. setupAnimation()
  232.  
  233. })()

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.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

About CodeHim

Free Web Design Code & Scripts - CodeHim is one of the BEST developer websites that provide web designers and developers with a simple way to preview and download a variety of free code & scripts. All codes published on CodeHim are open source, distributed under OSD-compliant license which grants all the rights to use, study, change and share the software in modified and unmodified form. Before publishing, we test and review each code snippet to avoid errors, but we cannot warrant the full correctness of all content. All trademarks, trade names, logos, and icons are the property of their respective owners... find out more...

Please Rel0ad/PressF5 this page if you can't click the download/preview link

X