├── .gitignore ├── README.md ├── css └── base.css ├── favicon.ico ├── img ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg ├── index.html └── js ├── demo.js └── imagesloaded.pkgd.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smooth scrolling and inner image animation 2 | 3 | Demo for the [tutorial](https://tympanus.net/codrops/?p=41030) on how to add smooth page scrolling with an inner image animation. 4 | 5 | ![Smooth scrolling](https://tympanus.net/codrops/wp-content/uploads/2019/07/ScrollAnimation_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=41030) 8 | 9 | [Demo](http://tympanus.net/Tutorials/SmoothScrollAnimations/) 10 | 11 | ## Credits 12 | 13 | - [imagesLoaded](https://imagesloaded.desandro.com/) by Dave DeSandro 14 | - Images from [Unsplash.com](https://unsplash.com/) 15 | 16 | ## License 17 | This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible mention and link to the original work. Always consider the licenses of all included libraries, scripts and images used. 18 | 19 | ## Misc 20 | 21 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [Google+](https://plus.google.com/101095823814290637419), [GitHub](https://github.com/codrops), [Pinterest](http://www.pinterest.com/codrops/), [Instagram](https://www.instagram.com/codropsss/) 22 | 23 | 24 | [© Codrops 2019](http://www.codrops.com) 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 16px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #1c1b1b; 14 | --color-bg: #efe6e1; 15 | --color-link: #b07656; 16 | --color-link-hover: #000; 17 | --color-deco: #e4dbd6; 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: paralucent, sans-serif; 21 | font-family: quiroh, sans-serif; 22 | font-family: mr-eaves-xl-sans, sans-serif; 23 | } 24 | 25 | /* Page Loader */ 26 | .js .loading::before { 27 | content: ''; 28 | position: fixed; 29 | z-index: 100000; 30 | top: 0; 31 | left: 0; 32 | width: 100%; 33 | height: 100%; 34 | background: var(--color-bg); 35 | } 36 | 37 | .js .loading::after { 38 | content: ''; 39 | position: fixed; 40 | z-index: 100000; 41 | top: 50%; 42 | left: 50%; 43 | width: 60px; 44 | height: 60px; 45 | margin: -30px 0 0 -30px; 46 | pointer-events: none; 47 | border-radius: 50%; 48 | opacity: 0.4; 49 | background: var(--color-link); 50 | animation: loaderAnim 0.7s linear infinite alternate forwards; 51 | } 52 | 53 | @keyframes loaderAnim { 54 | to { 55 | opacity: 1; 56 | transform: scale3d(0.5,0.5,1); 57 | } 58 | } 59 | 60 | a { 61 | text-decoration: none; 62 | color: var(--color-link); 63 | outline: none; 64 | } 65 | 66 | a:hover, 67 | a:focus { 68 | color: var(--color-link-hover); 69 | outline: none; 70 | } 71 | 72 | [data-scroll] { 73 | will-change: transform; 74 | } 75 | 76 | .frame { 77 | padding: 2.5rem 3rem; 78 | position: absolute; 79 | z-index: 10; 80 | } 81 | 82 | .frame__title { 83 | font-size: 1rem; 84 | margin: 0 0 2.5rem; 85 | } 86 | 87 | .frame__links { 88 | display: flex; 89 | flex-direction: column; 90 | align-items: flex-start; 91 | } 92 | 93 | .frame a { 94 | margin: 0.25rem 0; 95 | text-transform: lowercase; 96 | } 97 | 98 | .frame__github, 99 | .frame__links a:not(:last-child), 100 | .frame__demos a:not(:last-child) { 101 | margin-right: 1rem; 102 | } 103 | 104 | .frame__demos { 105 | margin: 1rem 0; 106 | } 107 | 108 | .frame__demo--current, 109 | .frame__demo--current:hover { 110 | color: var(--color-text); 111 | } 112 | 113 | .content { 114 | display: flex; 115 | flex-direction: column; 116 | position: relative; 117 | align-items: center; 118 | padding: 12rem 0; 119 | counter-reset: figure; 120 | } 121 | 122 | .item { 123 | margin: 10vh auto; 124 | max-width: 100%; 125 | position: relative; 126 | will-change: transform; 127 | } 128 | 129 | .item::before { 130 | counter-increment: figure; 131 | content: counter(figure, decimal-leading-zero); 132 | position: absolute; 133 | font-family: paralucent, sans-serif; 134 | font-size: 10rem; 135 | color: var(--color-deco); 136 | bottom: calc(100% - 3rem); 137 | } 138 | 139 | .item:nth-child(even)::before { 140 | right: 0; 141 | } 142 | 143 | .item__img-wrap { 144 | --aspect-ratio: 1/1.5; 145 | overflow: hidden; 146 | width: 500px; 147 | margin: 0 auto; 148 | padding-bottom: calc(100% / (var(--aspect-ratio))); 149 | max-width: calc(100% - 2rem); 150 | will-change: transform; 151 | } 152 | 153 | .item:first-child .item__img-wrap { 154 | --aspect-ratio: 8/10; 155 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/1.jpg); 156 | } 157 | 158 | .item:nth-child(2) .item__img-wrap { 159 | width: 1000px; 160 | --aspect-ratio: 120/76; 161 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/2.jpg); 162 | } 163 | 164 | .item:nth-child(3) .item__img-wrap { 165 | --aspect-ratio: 60/75; 166 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/3.jpg); 167 | } 168 | 169 | .item:nth-child(4) .item__img-wrap { 170 | width: 800px; 171 | --aspect-ratio: 900/505; 172 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/4.jpg); 173 | } 174 | 175 | .item:nth-child(5) .item__img-wrap { 176 | --aspect-ratio: 6/8; 177 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/5.jpg); 178 | } 179 | 180 | .item:nth-child(6) .item__img-wrap { 181 | width: calc(100vw - 2rem); 182 | --aspect-ratio: 1500/844; 183 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/6.jpg); 184 | } 185 | 186 | .item:nth-child(7) .item__img-wrap { 187 | width: 900px; 188 | --aspect-ratio: 1000/749; 189 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/7.jpg); 190 | } 191 | 192 | .item:nth-child(8) .item__img-wrap { 193 | width: 900px; 194 | --aspect-ratio: 1000/562; 195 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/8.jpg); 196 | } 197 | 198 | .item:nth-child(9) .item__img-wrap { 199 | --aspect-ratio: 60/75; 200 | --image: url(https://tympanus.net/Tutorials/SmoothScrollAnimations/img/9.jpg); 201 | } 202 | 203 | .item__img { 204 | --overflow: 40px; 205 | height: calc(100% + (2 * var(--overflow))); 206 | top: calc( -1 * var(--overflow)); 207 | width: 100%; 208 | position: absolute; 209 | background-image: var(--image); 210 | background-size: cover; 211 | background-position: 50% 0%; 212 | will-change: transform; 213 | } 214 | 215 | .item__img--t1 { 216 | --overflow: 60px; 217 | } 218 | 219 | .item__img--t2 { 220 | --overflow: 80px; 221 | } 222 | 223 | .item__img--t3 { 224 | --overflow: 120px; 225 | } 226 | 227 | .item__caption { 228 | padding: 2rem 1rem; 229 | } 230 | 231 | .item__caption-title { 232 | font-family: paralucent, sans-serif; 233 | font-weight: 400; 234 | font-size: 3rem; 235 | margin: 0; 236 | } 237 | 238 | .item__caption-copy { 239 | margin: 0; 240 | } 241 | 242 | .item__caption-copy::before { 243 | content: '__'; 244 | line-height: 1; 245 | color: var(--color-link); 246 | font-weight: 700; 247 | font-size: 3rem; 248 | margin: 0 0 1rem; 249 | display: block; 250 | } 251 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/favicon.ico -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/1.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/4.jpg -------------------------------------------------------------------------------- /img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/5.jpg -------------------------------------------------------------------------------- /img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/6.jpg -------------------------------------------------------------------------------- /img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/7.jpg -------------------------------------------------------------------------------- /img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/8.jpg -------------------------------------------------------------------------------- /img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollAnimations/06743813ac0a13bf94e021c56baa4419097e99fb/img/9.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth scrolling and inner image animation | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |

Smooth scrolling and inner image animation

21 |
22 | 27 |
28 |
29 |
30 |
31 |
32 |

Central view

33 |

Great turbulent clouds emerged into consciousness citizens.

34 |
35 |
36 |
37 |
38 |
39 |

Lost in time

40 |

Brain is the seed of intelligence the sky calls to us a very small stage.

41 |
42 |
43 |
44 |
45 |
46 |

Ready to land

47 |

Cosmos encyclopaedia galactica a billion trillion culture cosmic ocean.

48 |
49 |
50 |
51 |
52 |
53 |

All equal

54 |

Network of wormholes dream of the mind's eye finite but unbounded concept.

55 |
56 |
57 |
58 |
59 |
60 |

Connections

61 |

Two ghostly white figures in coveralls and helmets are softly dancing vastness.

62 |
63 |
64 |
65 |
66 |
67 |

The state of divergence

68 |

Vastness is bearable only through love invent the universe vanquish.

69 |
70 |
71 |
72 |
73 |
74 |

Open perspective

75 |

The only home we've ever known concept of the number one.

76 |
77 |
78 |
79 |
80 |
81 |

Discovery of shapes

82 |

Decipherment explorations tesseract as a patch of light.

83 |
84 |
85 |
86 |
87 |
88 |

The Observer

89 |

Astonishment muse about dispassionate extraterrestrial observer.

90 |
91 |
92 |
93 |
94 |
95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /js/demo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo.js 3 | * http://www.codrops.com 4 | * 5 | * Licensed under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Copyright 2019, Codrops 9 | * http://www.codrops.com 10 | */ 11 | { 12 | // helper functions 13 | const MathUtils = { 14 | // map number x from range [a, b] to [c, d] 15 | map: (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c, 16 | // linear interpolation 17 | lerp: (a, b, n) => (1 - n) * a + n * b 18 | }; 19 | 20 | // body element 21 | const body = document.body; 22 | 23 | // calculate the viewport size 24 | let winsize; 25 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 26 | calcWinsize(); 27 | // and recalculate on resize 28 | window.addEventListener('resize', calcWinsize); 29 | 30 | // scroll position and update function 31 | let docScroll; 32 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 33 | window.addEventListener('scroll', getPageYScroll); 34 | 35 | // Item 36 | class Item { 37 | constructor(el) { 38 | // the .item element 39 | this.DOM = {el: el}; 40 | // the inner image 41 | this.DOM.image = this.DOM.el.querySelector('.item__img'); 42 | this.renderedStyles = { 43 | // here we define which property will change as we scroll the page and the items is inside the viewport 44 | // in this case we will be translating the image on the y-axis 45 | // we interpolate between the previous and current value to achieve a smooth effect 46 | innerTranslationY: { 47 | // interpolated value 48 | previous: 0, 49 | // current value 50 | current: 0, 51 | // amount to interpolate 52 | ease: 0.1, 53 | // the maximum value to translate the image is set in a CSS variable (--overflow) 54 | maxValue: parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10), 55 | // current value setter 56 | // the value of the translation will be: 57 | // when the item's top value (relative to the viewport) equals the window's height (items just came into the viewport) the translation = minimum value (- maximum value) 58 | // when the item's top value (relative to the viewport) equals "-item's height" (item just exited the viewport) the translation = maximum value 59 | setValue: () => { 60 | const maxValue = this.renderedStyles.innerTranslationY.maxValue; 61 | const minValue = -1 * maxValue; 62 | return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, minValue, maxValue), maxValue), minValue) 63 | } 64 | } 65 | }; 66 | // set the initial values 67 | this.update(); 68 | // use the IntersectionObserver API to check when the element is inside the viewport 69 | // only then the element translation will be updated 70 | this.observer = new IntersectionObserver((entries) => { 71 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 72 | }); 73 | this.observer.observe(this.DOM.el); 74 | // init/bind events 75 | this.initEvents(); 76 | } 77 | update() { 78 | // gets the item's height and top (relative to the document) 79 | this.getSize(); 80 | // sets the initial value (no interpolation) 81 | for (const key in this.renderedStyles ) { 82 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 83 | } 84 | // translate the image 85 | this.layout(); 86 | } 87 | getSize() { 88 | const rect = this.DOM.el.getBoundingClientRect(); 89 | this.props = { 90 | // item's height 91 | height: rect.height, 92 | // offset top relative to the document 93 | top: docScroll + rect.top 94 | } 95 | } 96 | initEvents() { 97 | window.addEventListener('resize', () => this.resize()); 98 | } 99 | resize() { 100 | // on resize rest sizes and update the translation value 101 | this.update(); 102 | } 103 | render() { 104 | // update the current and interpolated values 105 | for (const key in this.renderedStyles ) { 106 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 107 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 108 | } 109 | // and translates the image 110 | this.layout(); 111 | } 112 | layout() { 113 | // translates the image 114 | this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`; 115 | } 116 | } 117 | 118 | // SmoothScroll 119 | class SmoothScroll { 120 | constructor() { 121 | // the
element 122 | this.DOM = {main: document.querySelector('main')}; 123 | // the scrollable element 124 | // we translate this element when scrolling (y-axis) 125 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 126 | // the items on the page 127 | this.items = []; 128 | [...this.DOM.main.querySelectorAll('.content > .item')].forEach(item => this.items.push(new Item(item))); 129 | // here we define which property will change as we scroll the page 130 | // in this case we will be translating on the y-axis 131 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 132 | this.renderedStyles = { 133 | translationY: { 134 | // interpolated value 135 | previous: 0, 136 | // current value 137 | current: 0, 138 | // amount to interpolate 139 | ease: 0.1, 140 | // current value setter 141 | // in this case the value of the translation will be the same like the document scroll 142 | setValue: () => docScroll 143 | } 144 | }; 145 | // set the body's height 146 | this.setSize(); 147 | // set the initial values 148 | this.update(); 149 | // the
element's style needs to be modified 150 | this.style(); 151 | // init/bind events 152 | this.initEvents(); 153 | // start the render loop 154 | requestAnimationFrame(() => this.render()); 155 | } 156 | update() { 157 | // sets the initial value (no interpolation) - translate the scroll value 158 | for (const key in this.renderedStyles ) { 159 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 160 | } 161 | // translate the scrollable element 162 | this.layout(); 163 | } 164 | layout() { 165 | // translates the scrollable element 166 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 167 | } 168 | setSize() { 169 | // set the heigh of the body in order to keep the scrollbar on the page 170 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 171 | } 172 | style() { 173 | // the
needs to "stick" to the screen and not scroll 174 | // for that we set it to position fixed and overflow hidden 175 | this.DOM.main.style.position = 'fixed'; 176 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 177 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 178 | this.DOM.main.style.overflow = 'hidden'; 179 | } 180 | initEvents() { 181 | // on resize reset the body's height 182 | window.addEventListener('resize', () => this.setSize()); 183 | } 184 | render() { 185 | // update the current and interpolated values 186 | for (const key in this.renderedStyles ) { 187 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 188 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 189 | } 190 | // and translate the scrollable element 191 | this.layout(); 192 | 193 | // for every item 194 | for (const item of this.items) { 195 | // if the item is inside the viewport call it's render function 196 | // this will update the item's inner image translation, based on the document scroll value and the item's position on the viewport 197 | if ( item.isVisible ) { 198 | item.render(); 199 | } 200 | } 201 | 202 | // loop.. 203 | requestAnimationFrame(() => this.render()); 204 | } 205 | } 206 | 207 | /***********************************/ 208 | /********** Preload stuff **********/ 209 | 210 | // Preload images 211 | const preloadImages = () => { 212 | return new Promise((resolve, reject) => { 213 | imagesLoaded(document.querySelectorAll('.item__img'), {background: true}, resolve); 214 | }); 215 | }; 216 | 217 | // And then.. 218 | preloadImages().then(() => { 219 | // Remove the loader 220 | document.body.classList.remove('loading'); 221 | // Get the scroll position 222 | getPageYScroll(); 223 | // Initialize the Smooth Scrolling 224 | new SmoothScroll(); 225 | }); 226 | } -------------------------------------------------------------------------------- /js/imagesloaded.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v4.1.1 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | !function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=0,o=i[n];e=e||[];for(var r=this._onceEvents&&this._onceEvents[t];o;){var s=r&&r[o];s&&(this.off(t,o),delete r[o]),o.apply(this,e),n+=s?0:1,o=i[n]}return this}},t}),function(t,e){"use strict";"function"==typeof define&&define.amd?define(["ev-emitter/ev-emitter"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}(window,function(t,e){function i(t,e){for(var i in e)t[i]=e[i];return t}function n(t){var e=[];if(Array.isArray(t))e=t;else if("number"==typeof t.length)for(var i=0;i