├── .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 |
--------------------------------------------------------------------------------