├── .gitignore ├── LICENSE ├── README.md ├── css └── styles.css ├── gulpfile.js ├── img ├── image1.jpg ├── image10.jpg ├── image2.jpg ├── image3.jpg ├── image4.jpg ├── image5.jpg ├── image6.jpg ├── image7.jpg ├── image8.jpg └── image9.jpg ├── index.html ├── js ├── scripts.js └── vh-fix.js ├── package.json └── scss └── styles.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lmgonzalves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scroll Based Animation 2 | 3 | Scroll Based Animation with JavaScript 4 | 5 | - **Tutorial**: [https://scotch.io/tutorials/implementing-a-scroll-based-animation-with-javascript](https://scotch.io/tutorials/implementing-a-scroll-based-animation-with-javascript) 6 | - **Demo**: [https://lmgonzalves.github.io/scroll-based-animation/](https://lmgonzalves.github.io/scroll-based-animation/) 7 | - **Codepen**: [https://codepen.io/lmgonzalves/pen/QPBPJe](https://codepen.io/lmgonzalves/pen/QPBPJe) 8 | 9 | ## Credits 10 | 11 | - The demo was inspired by this web page: [Jorik](https://www.jorik.com/blogs/lookbook/spring-summer-2019). 12 | - All the images are from [Unsplash](https://unsplash.com). 13 | - The SVG background used was customized at [SVGBackgrounds.com](https://www.svgbackgrounds.com/). 14 | -------------------------------------------------------------------------------- /css/styles.css: -------------------------------------------------------------------------------- 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 | 37 | .image:nth-child(2n) { 38 | justify-self: start; 39 | } 40 | 41 | .image:nth-child(1) { 42 | background-image: url("../img/image1.jpg"); 43 | } 44 | 45 | .image:nth-child(2) { 46 | background-image: url("../img/image2.jpg"); 47 | } 48 | 49 | .image:nth-child(3) { 50 | background-image: url("../img/image3.jpg"); 51 | } 52 | 53 | .image:nth-child(4) { 54 | background-image: url("../img/image4.jpg"); 55 | } 56 | 57 | .image:nth-child(5) { 58 | background-image: url("../img/image5.jpg"); 59 | } 60 | 61 | .image:nth-child(6) { 62 | background-image: url("../img/image6.jpg"); 63 | } 64 | 65 | .image:nth-child(7) { 66 | background-image: url("../img/image7.jpg"); 67 | } 68 | 69 | .image:nth-child(8) { 70 | background-image: url("../img/image8.jpg"); 71 | } 72 | 73 | .image:nth-child(9) { 74 | background-image: url("../img/image9.jpg"); 75 | } 76 | 77 | .image:nth-child(10) { 78 | background-image: url("../img/image10.jpg"); 79 | } 80 | 81 | @media screen and (max-width: 760px) { 82 | .container { 83 | grid-template-columns: 1fr; 84 | justify-items: center; 85 | } 86 | .image:nth-child(2n) { 87 | justify-self: center; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var browserSync = require('browser-sync') 3 | var sass = require('gulp-sass') 4 | var prefix = require('gulp-autoprefixer') 5 | 6 | gulp.task('serve', ['sass'], function () { 7 | browserSync.init({ 8 | server: { 9 | baseDir: './' 10 | }, 11 | open: false, 12 | online: false, 13 | notify: false 14 | }) 15 | 16 | gulp.watch('scss/*.scss', ['sass']) 17 | gulp.watch(['**/*.html', 'js/*']).on('change', browserSync.reload) 18 | }) 19 | 20 | gulp.task('sass', function () { 21 | return gulp.src('scss/*.scss') 22 | .pipe(sass({ 23 | outputStyle: 'expanded', 24 | includePaths: ['scss'] 25 | })) 26 | .pipe(prefix(['last 5 versions'], { cascade: true })) 27 | .pipe(gulp.dest('css')) 28 | .pipe(browserSync.reload({ stream: true })) 29 | }) 30 | 31 | gulp.task('default', ['serve']) 32 | -------------------------------------------------------------------------------- /img/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image1.jpg -------------------------------------------------------------------------------- /img/image10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image10.jpg -------------------------------------------------------------------------------- /img/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image2.jpg -------------------------------------------------------------------------------- /img/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image3.jpg -------------------------------------------------------------------------------- /img/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image4.jpg -------------------------------------------------------------------------------- /img/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image5.jpg -------------------------------------------------------------------------------- /img/image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image6.jpg -------------------------------------------------------------------------------- /img/image7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image7.jpg -------------------------------------------------------------------------------- /img/image8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image8.jpg -------------------------------------------------------------------------------- /img/image9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmgonzalves/scroll-based-animation/46db94e1fea06553aad7dad9b1504892dff0ae55/img/image9.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Scroll Based Animation 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /js/scripts.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Easing function used for `translateX` animation 4 | // From: https://gist.github.com/gre/1650294 5 | function easeOutQuad (t) { 6 | return t * (2 - t) 7 | } 8 | 9 | // Returns a random number (integer) between `min` and `max` 10 | function random (min, max) { 11 | return Math.floor(Math.random() * (max - min + 1)) + min 12 | } 13 | 14 | // Returns a random number as well, but it could be negative also 15 | function randomPositiveOrNegative (min, max) { 16 | return random(min, max) * (Math.random() > 0.5 ? 1 : -1) 17 | } 18 | 19 | // Set CSS `tranform` property for an element 20 | function setTransform (el, transform) { 21 | el.style.transform = transform 22 | el.style.WebkitTransform = transform 23 | } 24 | 25 | // Current scroll position 26 | var current = 0 27 | // Target scroll position 28 | var target = 0 29 | // Ease or speed for moving from `current` to `target` 30 | var ease = 0.075 31 | // Utility variables for `requestAnimationFrame` 32 | var rafId = undefined 33 | var rafActive = false 34 | // Container element 35 | var container = document.querySelector('.container') 36 | // Array with `.image` elements 37 | var images = Array.prototype.slice.call(document.querySelectorAll('.image')) 38 | // Variables for storing dimmensions 39 | var windowWidth, containerHeight, imageHeight 40 | 41 | // Variables for specifying transform parameters and limits 42 | var rotateXMaxList = [] 43 | var rotateYMaxList = [] 44 | var translateXMax = -200 45 | 46 | // Popullating the `rotateXMaxList` and `rotateYMaxList` with random values 47 | images.forEach(function () { 48 | rotateXMaxList.push(randomPositiveOrNegative(20, 40)) 49 | rotateYMaxList.push(randomPositiveOrNegative(20, 60)) 50 | }) 51 | 52 | // The `fakeScroll` is an element to make the page scrollable 53 | // Here we are creating it and appending it to the `body` 54 | var fakeScroll = document.createElement('div') 55 | fakeScroll.className = 'fake-scroll' 56 | document.body.appendChild(fakeScroll) 57 | // In the `setupAnimation` function (below) we will set the `height` properly 58 | 59 | // Geeting dimmensions and setting up all for animation 60 | function setupAnimation () { 61 | // Updating dimmensions 62 | windowWidth = window.innerWidth 63 | containerHeight = container.getBoundingClientRect().height 64 | imageHeight = containerHeight / (windowWidth > 760 ? images.length / 2 : images.length) 65 | // Set `height` for the fake scroll element 66 | fakeScroll.style.height = containerHeight + 'px' 67 | // Start the animation, if it is not running already 68 | startAnimation() 69 | } 70 | 71 | // Update scroll `target`, and start the animation if it is not running already 72 | function updateScroll () { 73 | target = window.scrollY || window.pageYOffset 74 | startAnimation() 75 | } 76 | 77 | // Start the animation, if it is not running already 78 | function startAnimation () { 79 | if (!rafActive) { 80 | rafActive = true 81 | rafId = requestAnimationFrame(updateAnimation) 82 | } 83 | } 84 | 85 | // Do calculations and apply CSS `transform`s accordingly 86 | function updateAnimation () { 87 | // Difference between `target` and `current` scroll position 88 | var diff = target - current 89 | // `delta` is the value for adding to the `current` scroll position 90 | // If `diff < 0.1`, make `delta = 0`, so the animation would not be endless 91 | var delta = Math.abs(diff) < 0.1 ? 0 : diff * ease 92 | 93 | if (delta) { // If `delta !== 0` 94 | // Update `current` scroll position 95 | current += delta 96 | // Round value for better performance 97 | current = parseFloat(current.toFixed(2)) 98 | // Call `update` again, using `requestAnimationFrame` 99 | rafId = requestAnimationFrame(updateAnimation) 100 | } else { // If `delta === 0` 101 | // Update `current`, and finish the animation loop 102 | current = target 103 | rafActive = false 104 | cancelAnimationFrame(rafId) 105 | } 106 | 107 | // Update images 108 | updateAnimationImages() 109 | 110 | // Set the CSS `transform` corresponding to the custom scroll effect 111 | setTransform(container, 'translateY('+ -current +'px)') 112 | } 113 | 114 | // Calculate the CSS `transform` values for each `image`, given the `current` scroll position 115 | function updateAnimationImages () { 116 | // This value is the `ratio` between `current` scroll position and image's `height` 117 | var ratio = current / imageHeight 118 | // Some variables for using in the loop 119 | var intersectionRatioIndex, intersectionRatioValue, intersectionRatio 120 | var rotateX, rotateXMax, rotateY, rotateYMax, translateX 121 | 122 | // For each `image` element, make calculations and set CSS `transform` accordingly 123 | images.forEach(function (image, index) { 124 | // Calculating the `intersectionRatio`, similar to the value provided by 125 | // the IntersectionObserver API 126 | intersectionRatioIndex = windowWidth > 760 ? parseInt(index / 2) : index 127 | intersectionRatioValue = ratio - intersectionRatioIndex 128 | intersectionRatio = Math.max(0, 1 - Math.abs(intersectionRatioValue)) 129 | // Calculate the `rotateX` value for the current `image` 130 | rotateXMax = rotateXMaxList[index] 131 | rotateX = rotateXMax - (rotateXMax * intersectionRatio) 132 | rotateX = rotateX.toFixed(2) 133 | // Calculate the `rotateY` value for the current `image` 134 | rotateYMax = rotateYMaxList[index] 135 | rotateY = rotateYMax - (rotateYMax * intersectionRatio) 136 | rotateY = rotateY.toFixed(2) 137 | // Calculate the `translateX` value for the current `image` 138 | if (windowWidth > 760) { 139 | translateX = translateXMax - (translateXMax * easeOutQuad(intersectionRatio)) 140 | translateX = translateX.toFixed(2) 141 | } else { 142 | translateX = 0 143 | } 144 | // Invert `rotateX` and `rotateY` values in case the image is below the center of the viewport 145 | // Also update `translateX` value, to achieve an alternating effect 146 | if (intersectionRatioValue < 0) { 147 | rotateX = -rotateX 148 | rotateY = -rotateY 149 | translateX = index % 2 ? -translateX : 0 150 | } else { 151 | translateX = index % 2 ? 0 : translateX 152 | } 153 | // Set the CSS `transform`, using calculated values 154 | setTransform(image, 'perspective(500px) translateX('+ translateX +'px) rotateX('+ rotateX +'deg) rotateY('+ rotateY +'deg)') 155 | }) 156 | } 157 | 158 | // Listen for `resize` event to recalculate dimmensions 159 | window.addEventListener('resize', setupAnimation) 160 | // Listen for `scroll` event to update `target` scroll position 161 | window.addEventListener('scroll', updateScroll) 162 | 163 | // Initial setup 164 | setupAnimation() 165 | 166 | })() 167 | -------------------------------------------------------------------------------- /js/vh-fix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Regex tested and matched against the following userAgents: 5 | * iPhone 6 | * Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) 7 | * AppleWebKit/602.1.50 (KHTML, like Gecko) 8 | * CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1 9 | * iPad 10 | * Mozilla/5.0 (iPad; CPU OS 9_0 like Mac OS X) 11 | * AppleWebKit/600.1.4 (KHTML, like Gecko) 12 | * CriOS/45.0.2454.89 Mobile/13A344 Safari/600.1.4 (000205) 13 | */ 14 | 15 | const iOSChromeDetected = /CriOS/.test(navigator.userAgent); 16 | 17 | if (iOSChromeDetected) { 18 | const getHeight = function getComputedHeightFrom(element) { 19 | const computedHeightString = getComputedStyle(element).height; 20 | const elementHeight = Number(computedHeightString.replace('px', '')); 21 | return elementHeight; 22 | }; 23 | 24 | const calculateVh = function calculateVhFrom(elementHeight) { 25 | const approximateVh = (elementHeight / initialViewportHeight) * 100; 26 | const elementVh = Math.round(approximateVh); 27 | return elementVh; 28 | }; 29 | 30 | const setDataAttribute = function setDataAttributeUsing(elementVh, element) { 31 | const dataAttributeValue = `${elementVh}`; 32 | element.setAttribute('data-vh', dataAttributeValue); 33 | }; 34 | 35 | const setHeight = function setHeightBasedOnVh(element) { 36 | const landscape = orientation; 37 | const vhRatio = Number(element.dataset.vh / 100); 38 | if (landscape) { 39 | element.style.height = `${vhRatio * landscapeHeight}px`; 40 | } else { 41 | element.style.height = `${vhRatio * portraitHeight}px`; 42 | } 43 | }; 44 | 45 | const initialize = function initializeDataAttributeAndHeight(element) { 46 | const elementHeight = getHeight(element); 47 | const elementVh = calculateVh(elementHeight); 48 | setDataAttribute(elementVh, element); 49 | setHeight(element); 50 | }; 51 | 52 | const initialViewportHeight = window.innerHeight; 53 | const elements = Array.from(document.getElementsByClassName('vh-fix')); 54 | const statusBarHeight = 20; 55 | const portraitHeight = screen.height - statusBarHeight; 56 | const landscapeHeight = screen.width - statusBarHeight; 57 | 58 | window.onload = function() { 59 | window.addEventListener('orientationchange', function() { 60 | elements.forEach(setHeight); 61 | }); 62 | 63 | elements.forEach(initialize); 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroll-based-animation", 3 | "version": "0.0.1", 4 | "description": "Scroll Based Animation with JavaScript", 5 | "main": "js/scripts.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/lmgonzalves/scroll-based-animation.git" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "animation", 16 | "scroll" 17 | ], 18 | "author": "lmgonzalves", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/lmgonzalves/scroll-based-animation/issues" 22 | }, 23 | "homepage": "https://github.com/lmgonzalves/scroll-based-animation", 24 | "devDependencies": { 25 | "browser-sync": "^2.23.6", 26 | "gulp": "^3.9.1", 27 | "gulp-autoprefixer": "^5.0.0", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-sass": "^3.1.0", 30 | "gulp-uglify": "^3.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scss/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | // `overflow-x` should be hidden, so horizontal scrollbar 3 | // don't appears when performing transformations 4 | overflow-x: hidden; 5 | 6 | // Setup the background 7 | height: 100vh; 8 | background-color: #9f9eac; 9 | 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"); 10 | /* background by SVGBackgrounds.com */ 11 | background-attachment: fixed; 12 | background-position: center; 13 | background-size: cover; 14 | } 15 | 16 | // The styles for a `div` element (inserted with Javascript) 17 | // Used to make the page scrollable 18 | // Will be setted a proper `height` value using Javascript 19 | .fake-scroll { 20 | position: absolute; 21 | top: 0; 22 | width: 1px; 23 | } 24 | 25 | // The container for all images 26 | .container { 27 | // 2 columns grid 28 | display: grid; 29 | grid-template-columns: 1fr 1fr; 30 | grid-gap: 0 10%; 31 | justify-items: end; // This will align all items (images) to the right 32 | 33 | // Fixed positioned, so it won't be affected by default scroll 34 | // It will be moved using `transform`, to achieve a custom scroll behavior 35 | position: fixed; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | } 40 | 41 | // Styles for image elements 42 | // Mainly positioning and background styles 43 | .image { 44 | position: relative; 45 | width: 300px; 46 | height: 100vh; 47 | background-repeat: no-repeat; 48 | background-position: center; 49 | 50 | // This will align all even images to the left 51 | // For getting centered positioned images, respect the viewport 52 | &:nth-child(2n) { 53 | justify-self: start; 54 | } 55 | 56 | // Set each `background-image` using a SCSS `for` loop 57 | @for $i from 1 through 10 { 58 | &:nth-child(#{$i}) { 59 | background-image: url('../img/image#{$i}.jpg'); 60 | } 61 | } 62 | } 63 | 64 | // Adjusting layout for small screens 65 | @media screen and (max-width: 760px) { 66 | .container { 67 | // 1 column grid 68 | grid-template-columns: 1fr; 69 | // Fix image centering 70 | justify-items: center; 71 | } 72 | 73 | // Fix image centering 74 | .image:nth-child(2n) { 75 | justify-self: center; 76 | } 77 | } 78 | --------------------------------------------------------------------------------