├── .gitignore ├── favicon.ico ├── img ├── 1.jpg ├── 10.jpg ├── 11.jpg ├── 12.jpg ├── 13.jpg ├── 14.jpg ├── 15.jpg ├── 16.jpg ├── 17.jpg ├── 18.jpg ├── 19.jpg ├── 2.jpg ├── 20.jpg ├── 21.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg └── 9.jpg ├── README.md ├── index4.html ├── js ├── imagesloaded.pkgd.min.js ├── demo.js ├── demo3.js ├── demo5.js ├── demo4.js ├── demo2.js └── demo6.js ├── index.html ├── index6.html ├── index3.html ├── index2.html ├── index5.html └── css └── base.css /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/1.jpg -------------------------------------------------------------------------------- /img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/10.jpg -------------------------------------------------------------------------------- /img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/11.jpg -------------------------------------------------------------------------------- /img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/12.jpg -------------------------------------------------------------------------------- /img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/13.jpg -------------------------------------------------------------------------------- /img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/14.jpg -------------------------------------------------------------------------------- /img/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/15.jpg -------------------------------------------------------------------------------- /img/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/16.jpg -------------------------------------------------------------------------------- /img/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/17.jpg -------------------------------------------------------------------------------- /img/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/18.jpg -------------------------------------------------------------------------------- /img/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/19.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/2.jpg -------------------------------------------------------------------------------- /img/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/20.jpg -------------------------------------------------------------------------------- /img/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/21.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/4.jpg -------------------------------------------------------------------------------- /img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/5.jpg -------------------------------------------------------------------------------- /img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/6.jpg -------------------------------------------------------------------------------- /img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/7.jpg -------------------------------------------------------------------------------- /img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/8.jpg -------------------------------------------------------------------------------- /img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/SmoothScrollingImageEffects/HEAD/img/9.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smooth Scrolling Image Effects 2 | 3 | A small set of ideas on animating images and other elements while smooth scrolling a page. 4 | 5 | ![Smooth Scrolling Image Effects](https://tympanus.net/codrops/wp-content/uploads/2019/07/SmoothScrollingEffects_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=42459) 8 | 9 | [Demo](http://tympanus.net/Development/SmoothScrollingImageEffects/) 10 | 11 | ## Credits 12 | 13 | - [TweenMax](https://greensock.com/tweenmax) by Greensock 14 | - [imagesLoaded](https://imagesloaded.desandro.com/) by Dave DeSandro 15 | - Images by [Frankie Cordoba](https://www.instagram.com/byfoul/), [Unsplash profile](https://unsplash.com/@byfoul) 16 | 17 | ## License 18 | 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. 19 | 20 | ## Misc 21 | 22 | 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/) 23 | 24 | 25 | [© Codrops 2019](http://www.codrops.com) 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |

Smooth Scrolling Image Effects

23 |
24 | 29 |
30 | 01 31 | 02 32 | 03 33 | 04 34 | 05 35 | 06 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

Wa

44 |
45 |
46 |
47 |
48 |
49 |

Ks

50 |
51 |
52 |
53 |
54 |
55 |

Nc

56 |
57 |
58 |
59 |
60 |
61 |

Ga

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

De

68 |
69 |
70 |
71 |
72 |
73 |

Nj

74 |
75 |
76 |
77 |
78 |
79 |

Al

80 |
81 |
82 |
83 |
84 |
85 |

Sc

86 |
87 |
88 |
89 |
90 |
91 |

Vt

92 |
93 |
94 |
95 |

Frankie Cordoba

96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /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 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Smooth Scrolling Image Effects

19 |
20 | 25 |
26 | 01 27 | 02 28 | 03 29 | 04 30 | 05 31 | 06 32 |
33 |
34 |
35 |
36 | 01 37 |
38 |
39 |

Oh

40 |

Little trees and bushes grow however makes them happy.

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

Ri

47 |

We don't have to be committed. We are just playing here.

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

Nj

54 |

I thought today we would do a happy little picture.

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

Mo

61 |

Nature is so fantastic, enjoy it. Let it make you happy.

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

Ne

68 |

We need a shadow side and a highlight side.

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

Wy

75 |

We'll put some happy little leaves here and there.

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

Tx

82 |

With something so strong, a little bit can go a long way.

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

Sd

89 |

There are no limits in this world.

90 |
91 |

Photography by Frankie Cordoba

92 |
93 |
94 |
95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /index6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 5 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |

Smooth Scrolling Image Effects

23 |
24 | 29 |
30 | 01 31 | 02 32 | 03 33 | 04 34 | 05 35 | 06 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

Wa

44 |

A little happy sunlight shining through there. In nature, dead trees are just as normal as live trees.

45 |
46 |
47 |
48 |
49 |
50 |
51 |

Ks

52 |

In your imagination you can go anywhere you want. You can create beautiful things - but you have to see them in your mind first.

53 |
54 |
55 |
56 |
57 |
58 |
59 |

Nc

60 |

Just let go - and fall like a little waterfall. Painting should do one thing. It should put happiness in your heart.

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

Ga

68 |

You don't have to be crazy to do this but it does help. That's a son of a gun of a cloud.

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

De

76 |

Steve wants reflections, so let's give him reflections. Let the paint work.

77 |
78 |
79 |
80 |
81 |
82 |
83 |

Nj

84 |

No pressure. Just relax and watch it happen. If there's two big trees invariably sooner or later there's gonna be a little tree.

85 |
86 |
87 |
88 |
89 |
90 |
91 |

Al

92 |

Now it's beginning to make a little sense. Painting should do one thing. It should put happiness in your heart.

93 |
94 |
95 |
96 |
97 |
98 |
99 |

Sc

100 |

Volunteering your time; it pays you and your whole community fantastic dividends. It's amazing what you can do with a little love in your heart.

101 |
102 |
103 |
104 |
105 |
106 |
107 |

Vt

108 |

Be so very light. Be a gentle whisper. You got your heavy coat out yet? It's getting colder.

109 |
110 |
111 |
112 |
113 |

Frankie Cordoba

114 |
115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 |

Smooth Scrolling Image Effects

23 |
24 | 29 |
30 | 01 31 | 02 32 | 03 33 | 04 34 | 05 35 | 06 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

De

44 |

A little happy sunlight shining through there. In nature, dead trees are just as normal as live trees.

45 |
46 |
47 |
48 |
49 |
50 |

Nj

51 |

All you have to learn here is how to have fun. No pressure. Just relax and watch it happen.

52 |
53 |
54 |
55 |
56 |
57 |

Al

58 |

Just let your mind wander and enjoy. This should make you happy. There are no mistakes. You can fix anything that happens.

59 |
60 |
61 |
62 |
63 |
64 |

Ut

65 |

We don't have to be committed. We are just playing here. Even the worst thing we can do here is good.

66 |
67 |
68 |
69 |
70 |
71 |

Wa

72 |

Decide where your cloud lives. Maybe he lives right in here. Little short strokes.

73 |
74 |
75 |
76 |
77 |
78 |

Ks

79 |

Let's build an almighty mountain. Everybody needs a friend.

80 |
81 |
82 |
83 |

Ca

84 |

Get away from those little Christmas tree things we used to make in school. We can fix anything.

85 |
86 |
87 |
88 |
89 |
90 |

Nc

91 |

In nature, dead trees are just as normal as live trees. The little tiny Tim easels will let you down.

92 |
93 |
94 |
95 |
96 |
97 |

Me

98 |

No worries. No cares. Just float and wait for the wind to blow you around. Get tough with it, get strong.

99 |
100 |
101 |
102 |
103 |
104 |

Ga

105 |

Let's make a happy little mountain now. These things happen automatically. All you have to do is just let them happen.

106 |
107 |

Photography by Frankie Cordoba

108 |
109 |
110 |
111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Smooth Scrolling Image Effects

19 |
20 | 25 |
26 | 01 27 | 02 28 | 03 29 | 04 30 | 05 31 | 06 32 |
33 |
34 |
35 |
36 |
37 |

Mi

38 |
39 |
40 |
41 |

De

42 |
43 |
44 |
45 |

Nj

46 |
47 |
48 |
49 |

Al

50 |
51 |
52 |
53 |

Ut

54 |
55 |
56 |
57 |

Wa

58 |
59 |
60 |
61 |

Ok

62 |
63 |
64 |
65 |

Nv

66 |
67 |
68 |
69 |

Ks

70 |
71 |
72 |
73 |

In

74 |
75 |
76 |
77 |

Me

78 |
79 |
80 |
81 |

Ca

82 |
83 |
84 |
85 |

Md

86 |
87 |
88 |
89 |

Ga

90 |
91 |
92 |
93 |

Co

94 |
95 |
96 |
97 |

Nc

98 |
99 |

Photography by Frankie Cordoba

100 |
101 |
102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /index5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Smooth Scrolling Image Effects | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Smooth Scrolling Image Effects

19 |
20 | 25 |
26 | 01 27 | 02 28 | 03 29 | 04 30 | 05 31 | 06 32 |
33 |
34 |
35 |
36 |
37 |

Mi

38 |
39 |
40 |
41 |

De

42 |
43 |
44 |
45 |

Nj

46 |
47 |
48 |
49 |

Al

50 |
51 |
52 |
53 |

Ut

54 |
55 |
56 |
57 |

Wa

58 |
59 |
60 |
61 |

Ok

62 |
63 |
64 |
65 |

Nv

66 |
67 |
68 |
69 |

Ks

70 |
71 |
72 |
73 |

In

74 |
75 |
76 |
77 |

Me

78 |
79 |
80 |
81 |

Ca

82 |
83 |
84 |
85 |

Md

86 |
87 |
88 |
89 |

Ga

90 |
91 |
92 |
93 |

Co

94 |
95 |
96 |
97 |

Nc

98 |
99 |

Photography by Frankie Cordoba

100 |
101 |
102 |
103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /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: #dc6e25; 14 | --color-bg: #efded3; 15 | --color-link: #000; 16 | --color-link-hover: #dc6e25; 17 | --aspect-ratio: 1/1.5; 18 | --imgwidthmax: 500px; 19 | --size-title: 10rem; 20 | --font-weight-title: 400; 21 | color: var(--color-text); 22 | background-color: var(--color-bg); 23 | font-family: turquoise, serif; 24 | font-size: 1.5rem; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } 28 | 29 | .demo-1 { 30 | --color-text: #111; 31 | --color-bg: #bad5ca; 32 | --color-link-hover: #5c4541; 33 | --aspect-ratio: 32/17; 34 | font-size: 1.25rem; 35 | font-family: ivymode, sans-serif; 36 | --size-title: 15vw; 37 | } 38 | 39 | .demo-2 { 40 | --blendmode-title: difference; 41 | --size-title: 12vw; 42 | } 43 | 44 | .demo-3 { 45 | --color-text: #905348; 46 | --color-bg: #120605; 47 | --color-link: #8f8f76; 48 | --color-link-hover: #fff; 49 | font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif; 50 | font-size: 1rem; 51 | --imgwidthmax: 400px; 52 | --color-description: #7e605b; 53 | --font-title: ivymode; 54 | --size-title: 9vw; 55 | --imgwidthmax: 600px; 56 | } 57 | 58 | .demo-4 { 59 | --color-text: #a9a798; 60 | --color-bg: #cccdb9; 61 | --color-link: #fff; 62 | --color-link-hover: #a9a798; 63 | --color-title: #fff; 64 | font-family: collier, sans-serif; 65 | --font-title: inherit; 66 | --font-weight-title: 100; 67 | font-size: 1.15rem; 68 | --imgwidthmax: 520px; 69 | } 70 | 71 | .demo-5 { 72 | --color-text: #f0f0f0; 73 | --color-bg: #000000; 74 | --color-link: #c5a45b; 75 | --color-link-hover: #f0f0f0; 76 | --color-title: #f0f0f0; 77 | font-family: input-mono, monospace; 78 | --font-title: inherit; 79 | font-size: 1.15rem; 80 | --aspect-ratio: 1/1.5; 81 | --font-weight-title: 800; 82 | } 83 | 84 | .demo-6 { 85 | --color-text: #e01723; 86 | --color-bg: #deaaad; 87 | --color-link: #c55b61; 88 | --color-link-hover: #1d1414; 89 | --color-title: #1d1414; 90 | font-family: ivymode, sans-serif; 91 | --font-title: inherit; 92 | font-size: 1.15rem; 93 | --aspect-ratio: 1/1.5; 94 | --imgwidthmax: 505px; 95 | } 96 | 97 | /* Page Loader */ 98 | .js .loading::before { 99 | content: ''; 100 | position: fixed; 101 | z-index: 100000; 102 | top: 0; 103 | left: 0; 104 | width: 100%; 105 | height: 100%; 106 | background: var(--color-bg); 107 | } 108 | 109 | .js .loading::after { 110 | content: ''; 111 | position: fixed; 112 | z-index: 100000; 113 | top: 50%; 114 | left: 50%; 115 | width: 60px; 116 | height: 60px; 117 | margin: -30px 0 0 -30px; 118 | pointer-events: none; 119 | border-radius: 50%; 120 | opacity: 0.4; 121 | background: var(--color-link); 122 | animation: loaderAnim 0.7s linear infinite alternate forwards; 123 | } 124 | 125 | @keyframes loaderAnim { 126 | to { 127 | opacity: 1; 128 | transform: scale3d(0.5,0.5,1); 129 | } 130 | } 131 | 132 | a { 133 | text-decoration: none; 134 | color: var(--color-link); 135 | outline: none; 136 | } 137 | 138 | a:hover, 139 | a:focus { 140 | color: var(--color-link-hover); 141 | outline: none; 142 | } 143 | 144 | .page { 145 | display: grid; 146 | padding: 5vw; 147 | max-width: 1400px; 148 | margin: 0 auto; 149 | grid-template-columns: 100%; 150 | grid-template-areas: 'header' 'meta' 'grid'; 151 | will-change: transform; 152 | } 153 | 154 | .page__title { 155 | grid-area: header; 156 | margin: 0 0 1rem; 157 | font-size: inherit; 158 | font-weight: 400; 159 | } 160 | 161 | .credits { 162 | text-align: center; 163 | } 164 | 165 | .credits--fixed { 166 | position: fixed; 167 | bottom: 5vw; 168 | margin: 0; 169 | left: 5vw; 170 | width: 90vw; 171 | text-align: right; 172 | } 173 | 174 | .credits--fixed::before { 175 | content: ''; 176 | background: currentColor; 177 | position: absolute; 178 | left: 0; 179 | bottom: 0; 180 | height: 5rem; 181 | width: 1px; 182 | } 183 | 184 | .meta { 185 | grid-area: meta; 186 | } 187 | 188 | .meta--center { 189 | justify-self: center; 190 | padding-top: 20vh; 191 | } 192 | 193 | .meta__links { 194 | display: flex; 195 | flex-wrap: wrap; 196 | line-height: 1.5; 197 | } 198 | 199 | .meta__links--column { 200 | flex-direction: column; 201 | } 202 | 203 | .meta__links a { 204 | margin: 0 1.5rem 0 0; 205 | } 206 | 207 | .meta__demos { 208 | margin-top: 1rem; 209 | } 210 | 211 | .meta__demo { 212 | display: inline-block; 213 | margin-right: 0.75rem; 214 | } 215 | 216 | .meta__demo--current { 217 | color: var(--color-link-hover); 218 | pointer-events: none; 219 | } 220 | 221 | .content { 222 | grid-area: grid; 223 | margin: 25vh 0 30vh; 224 | } 225 | 226 | .content--offset { 227 | display: grid; 228 | grid-template-columns: repeat(2, minmax(100px, 1fr)); 229 | grid-gap: 30vh 10vw; 230 | } 231 | 232 | .content--full { 233 | width: 100vw; 234 | justify-self: center; 235 | } 236 | 237 | .content--padded { 238 | padding: 0 10vw; 239 | } 240 | 241 | .content__item { 242 | --imgwidth: calc(var(--imgwidthmax) * var(--aspect-ratio)); 243 | width: var(--imgwidth); 244 | max-width: 100%; 245 | position: relative; 246 | will-change: transform; 247 | margin-bottom: 30vh; 248 | display: grid; 249 | grid-template-columns: 50% 50%; 250 | } 251 | 252 | .content__item--wide { 253 | grid-template-columns: 20% 80%; 254 | } 255 | 256 | .content__item--wide:nth-child(even) { 257 | grid-template-columns: 80% 20%; 258 | } 259 | 260 | .content--center .content__item { 261 | margin: 0 auto 60vh; 262 | } 263 | 264 | .content__item--expand { 265 | width: 100%; 266 | grid-template-columns: minmax(0, var(--imgwidth)) 1fr; 267 | } 268 | 269 | .content__item--expand:nth-child(even) { 270 | grid-template-columns: 1fr minmax(0, var(--imgwidth)); 271 | } 272 | 273 | .content--alternate .content__item { 274 | max-width: 90vw; 275 | } 276 | 277 | .content--offset .content__item { 278 | margin: 0 auto 15vh; 279 | } 280 | 281 | .content--offset .content__item:nth-child(even) { 282 | align-self: end; 283 | margin-bottom: -20vh; 284 | } 285 | 286 | .content--alternate .content__item:nth-child(even) { 287 | margin-left: auto; 288 | } 289 | 290 | .content__item-imgwrap { 291 | position: relative; 292 | --imgwidth: 100%; 293 | margin: 0 auto; 294 | grid-area: 1 / 1 / 3 / 3; 295 | overflow: hidden; 296 | width: var(--imgwidth); 297 | padding-bottom: calc(var(--imgwidth) / (var(--aspect-ratio))); 298 | will-change: transform; 299 | } 300 | 301 | .demo-2 .content__item-imgwrap { 302 | outline: 1px solid transparent; 303 | } 304 | 305 | .content__item--expand .content__item-imgwrap { 306 | grid-area: 1 / 1 / 3 / 2; 307 | } 308 | 309 | .content__item--expand:nth-child(even) .content__item-imgwrap { 310 | grid-area: 1 / 2 / 3 / 3; 311 | justify-self: end; 312 | } 313 | 314 | .content__item-img { 315 | --overflow: 40px; 316 | height: calc(100% + (2 * var(--overflow))); 317 | top: calc( -1 * var(--overflow)); 318 | width: 100%; 319 | position: absolute; 320 | background-size: cover; 321 | background-position: 50% 0%; 322 | will-change: transform; 323 | opacity: 0.8; 324 | } 325 | 326 | .content__item-img--t1 { 327 | --overflow: 60px; 328 | } 329 | 330 | .content__item-img--t2 { 331 | --overflow: 80px; 332 | } 333 | 334 | .content__item-img--t3 { 335 | --overflow: 120px; 336 | } 337 | 338 | .content__item-number { 339 | opacity: 0.03; 340 | font-size: 25vw; 341 | position: absolute; 342 | top: -7vw; 343 | right: -10vw; 344 | line-height: 1; 345 | } 346 | 347 | .content__item:nth-child(even) .content__item-number { 348 | right: auto; 349 | left: -7vw; 350 | } 351 | 352 | .content__item-title { 353 | position: relative; 354 | font-size: var(--size-title); 355 | padding: 0 3vw; 356 | margin: calc(var(--size-title) * -1/2) 0 0 0; 357 | align-self: start; 358 | line-height: 1; 359 | font-family: var(--font-title); 360 | font-weight: var(--font-weight-title); 361 | color: var(--color-title); 362 | will-change: transform; 363 | mix-blend-mode: var(--blendmode-title); 364 | } 365 | 366 | .content--center .content__item-title { 367 | grid-area: 1 / 1 / 3 / 3; 368 | margin: auto; 369 | } 370 | 371 | .content__item-title--layer { 372 | padding: 0; 373 | z-index: 10; 374 | grid-area: 1 / 1 / 2 / 3; 375 | width: 100%; 376 | text-align: center; 377 | } 378 | 379 | .content__item-description { 380 | grid-area: 3 / 1 / 3 / 3; 381 | width: 70%; 382 | position: relative; 383 | margin: 0; 384 | padding: 1rem 0 0 0; 385 | color: var(--color-description); 386 | } 387 | 388 | .content--alternate .content__item-title, 389 | .content__item--wide:nth-child(even) .content__item-description { 390 | grid-area: 3 / 1 / 4 / 2; 391 | justify-self: start; 392 | } 393 | 394 | .content--alternate .content__item:nth-child(even) .content__item-title, 395 | .content__item--wide .content__item-description { 396 | grid-area: 3 / 2 / 4 / 3; 397 | justify-self: end; 398 | width: auto; 399 | } 400 | 401 | .content__item--expand .content__item-description { 402 | grid-area: 1 / 2 / 3 / 3; 403 | justify-self: start; 404 | align-self: start; 405 | padding: 0 2rem; 406 | width: 250px; 407 | font-size: 0.9rem; 408 | } 409 | 410 | .content__item--expand:nth-child(even) .content__item-description { 411 | grid-area: 1 / 1 / 3 / 2; 412 | justify-self: end; 413 | text-align: right; 414 | } 415 | 416 | .content__item-deco { 417 | position: absolute; 418 | top: 2rem; 419 | left: 10vw; 420 | height: 30%; 421 | width: 1px; 422 | background: #d79612; 423 | } 424 | 425 | .content__item-decobar { 426 | width: 140%; 427 | height: 25%; 428 | top: 20%; 429 | left: -20%; 430 | position: absolute; 431 | background: #dd525a; 432 | mix-blend-mode: color-burn; 433 | } 434 | 435 | @media screen and (min-width: 53em) { 436 | .page--layout-1 { 437 | grid-template-columns: 30% 70%; 438 | grid-template-areas: 'header header' '... meta' 'grid grid'; 439 | } 440 | .page--layout-2 { 441 | grid-template-columns: 1fr 1fr; 442 | grid-template-areas: 'header meta' '... meta' 'grid grid'; 443 | } 444 | } 445 | 446 | @media screen and (max-width: 40em) { 447 | .content__item--expand .content__item-imgwrap, 448 | .content__item--expand:nth-child(even) .content__item-imgwrap { 449 | grid-area: 1 / 1 / 3 / 3; 450 | } 451 | .content__item--expand .content__item-description, 452 | .content__item--expand:nth-child(even) .content__item-description, 453 | .content__item--wide .content__item-description, 454 | .content__item--wide:nth-child(even) .content__item-description { 455 | grid-area: 3 / 1 / 4 / 3; 456 | padding: 1rem 0; 457 | width: 100%; 458 | text-align: left; 459 | } 460 | .content__item--wide .content__item-description { 461 | padding: 1rem; 462 | } 463 | .content--alternate .content__item-title, 464 | .content--alternate .content__item:nth-child(even) .content__item-title { 465 | grid-area: 1 / 1 / 4 / 2; 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | // the inner image 47 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 48 | this.DOM.imageWrapper = this.DOM.image.parentNode; 49 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 50 | this.renderedStyles = { 51 | // here we define which property will change as we scroll the page and the item is inside the viewport 52 | // in this case we will be: 53 | // - scaling the inner image 54 | // - translating the item's title 55 | // we interpolate between the previous and current value to achieve a smooth effect 56 | imageScale: { 57 | // interpolated value 58 | previous: 0, 59 | // current value 60 | current: 0, 61 | // amount to interpolate 62 | ease: 0.1, 63 | // current value setter 64 | setValue: () => { 65 | const toValue = 1.5; 66 | const fromValue = 1; 67 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 68 | return Math.max(Math.min(val, toValue), fromValue); 69 | } 70 | }, 71 | titleTranslationY: { 72 | previous: 0, 73 | current: 0, 74 | ease: 0.1, 75 | fromValue: Number(MathUtils.getRandomFloat(30,400)), 76 | setValue: () => { 77 | const fromValue = this.renderedStyles.titleTranslationY.fromValue; 78 | const toValue = -1*fromValue; 79 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 80 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 81 | } 82 | } 83 | }; 84 | // gets the item's height and top (relative to the document) 85 | this.getSize(); 86 | // set the initial values 87 | this.update(); 88 | // use the IntersectionObserver API to check when the element is inside the viewport 89 | // only then the element styles will be updated 90 | this.observer = new IntersectionObserver((entries) => { 91 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 92 | }); 93 | this.observer.observe(this.DOM.el); 94 | // init/bind events 95 | this.initEvents(); 96 | } 97 | update() { 98 | // sets the initial value (no interpolation) 99 | for (const key in this.renderedStyles ) { 100 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 101 | } 102 | // apply changes/styles 103 | this.layout(); 104 | } 105 | getSize() { 106 | const rect = this.DOM.el.getBoundingClientRect(); 107 | this.props = { 108 | // item's height 109 | height: rect.height, 110 | // offset top relative to the document 111 | top: docScroll + rect.top 112 | } 113 | } 114 | initEvents() { 115 | window.addEventListener('resize', () => this.resize()); 116 | } 117 | resize() { 118 | // gets the item's height and top (relative to the document) 119 | this.getSize(); 120 | // on resize reset sizes and update styles 121 | this.update(); 122 | } 123 | render() { 124 | // update the current and interpolated values 125 | for (const key in this.renderedStyles ) { 126 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 127 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 128 | } 129 | 130 | // and apply changes 131 | this.layout(); 132 | } 133 | layout() { 134 | // scale the image 135 | this.DOM.image.style.transform = `scale3d(${this.renderedStyles.imageScale.previous},${this.renderedStyles.imageScale.previous},1)`; 136 | // translate the title 137 | this.DOM.title.style.transform = `translate3d(0,${this.renderedStyles.titleTranslationY.previous}px,0)`; 138 | } 139 | } 140 | 141 | // SmoothScroll 142 | class SmoothScroll { 143 | constructor() { 144 | // the
element 145 | this.DOM = {main: document.querySelector('main')}; 146 | // the scrollable element 147 | // we translate this element when scrolling (y-axis) 148 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 149 | // the items on the page 150 | this.items = []; 151 | this.DOM.content = this.DOM.main.querySelector('.content'); 152 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 153 | // here we define which property will change as we scroll the page 154 | // in this case we will be translating on the y-axis 155 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 156 | this.renderedStyles = { 157 | translationY: { 158 | // interpolated value 159 | previous: 0, 160 | // current value 161 | current: 0, 162 | // amount to interpolate 163 | ease: 0.1, 164 | // current value setter 165 | // in this case the value of the translation will be the same like the document scroll 166 | setValue: () => docScroll 167 | } 168 | }; 169 | // set the body's height 170 | this.setSize(); 171 | // set the initial values 172 | this.update(); 173 | // the
element's style needs to be modified 174 | this.style(); 175 | // init/bind events 176 | this.initEvents(); 177 | // start the render loop 178 | requestAnimationFrame(() => this.render()); 179 | } 180 | update() { 181 | // sets the initial value (no interpolation) - translate the scroll value 182 | for (const key in this.renderedStyles ) { 183 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 184 | } 185 | // translate the scrollable element 186 | this.layout(); 187 | } 188 | layout() { 189 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 190 | } 191 | setSize() { 192 | // set the heigh of the body in order to keep the scrollbar on the page 193 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 194 | } 195 | style() { 196 | // the
needs to "stick" to the screen and not scroll 197 | // for that we set it to position fixed and overflow hidden 198 | this.DOM.main.style.position = 'fixed'; 199 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 200 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 201 | this.DOM.main.style.overflow = 'hidden'; 202 | } 203 | initEvents() { 204 | // on resize reset the body's height 205 | window.addEventListener('resize', () => this.setSize()); 206 | } 207 | render() { 208 | // Get scrolling speed 209 | // Update lastScroll 210 | scrollingSpeed = Math.abs(docScroll - lastScroll); 211 | lastScroll = docScroll; 212 | 213 | // update the current and interpolated values 214 | for (const key in this.renderedStyles ) { 215 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 216 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 217 | } 218 | // and translate the scrollable element 219 | this.layout(); 220 | 221 | // for every item 222 | for (const item of this.items) { 223 | // if the item is inside the viewport call it's render function 224 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 225 | if ( item.isVisible ) { 226 | if ( item.insideViewport ) { 227 | item.render(); 228 | } 229 | else { 230 | item.insideViewport = true; 231 | item.update(); 232 | } 233 | } 234 | else { 235 | item.insideViewport = false; 236 | } 237 | } 238 | 239 | // loop.. 240 | requestAnimationFrame(() => this.render()); 241 | } 242 | } 243 | 244 | /***********************************/ 245 | /********** Preload stuff **********/ 246 | 247 | // Preload images 248 | const preloadImages = () => { 249 | return new Promise((resolve, reject) => { 250 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 251 | }); 252 | }; 253 | 254 | // And then.. 255 | preloadImages().then(() => { 256 | // Remove the loader 257 | document.body.classList.remove('loading'); 258 | // Get the scroll position and update the lastScroll variable 259 | getPageYScroll(); 260 | lastScroll = docScroll; 261 | // Initialize the Smooth Scrolling 262 | new SmoothScroll(); 263 | }); 264 | } -------------------------------------------------------------------------------- /js/demo3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo3.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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 47 | this.DOM.imageWrapper = this.DOM.image.parentNode; 48 | // 3d stuff 49 | this.DOM.el.style.perspective = '1000px'; 50 | this.DOM.imageWrapper.style.transformOrigin = '50% 100%'; 51 | this.ry = MathUtils.getRandomFloat(-0.5,0.5); 52 | this.rz = MathUtils.getRandomFloat(-0.5,0.5); 53 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 54 | this.DOM.title.style.transform = 'translate3d(0,0,200px)'; 55 | this.renderedStyles = { 56 | // here we define which property will change as we scroll the page and the item is inside the viewport 57 | // in this case we will be: 58 | // - translating the inner image 59 | // - rotating the item 60 | // we interpolate between the previous and current value to achieve a smooth effect 61 | innerTranslationY: { 62 | // interpolated value 63 | previous: 0, 64 | // current value 65 | current: 0, 66 | // amount to interpolate 67 | ease: 0.1, 68 | // current value setter 69 | setValue: () => { 70 | // the maximum value to translate the image is set in a CSS variable (--overflow) 71 | const toValue = parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10); 72 | const fromValue = -1 * toValue; 73 | return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue), toValue), fromValue); 74 | } 75 | }, 76 | itemRotation: { 77 | // interpolated value 78 | previous: 0, 79 | // current value 80 | current: 0, 81 | // amount to interpolate 82 | ease: 0.1, 83 | toValue: Number(MathUtils.getRandomFloat(-70,-50)), 84 | // current value setter 85 | setValue: () => { 86 | const toValue = this.renderedStyles.itemRotation.toValue; 87 | const fromValue = toValue*-1; 88 | const val = MathUtils.map(this.props.top - docScroll, winsize.height*1.5, -1 * this.props.height, fromValue, toValue); 89 | return Math.min(Math.max(val, toValue), fromValue); 90 | } 91 | } 92 | }; 93 | // gets the item's height and top (relative to the document) 94 | this.getSize(); 95 | // set the initial values 96 | this.update(); 97 | // use the IntersectionObserver API to check when the element is inside the viewport 98 | // only then the element styles will be updated 99 | this.observer = new IntersectionObserver((entries) => { 100 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 101 | }); 102 | this.observer.observe(this.DOM.el); 103 | // init/bind events 104 | this.initEvents(); 105 | } 106 | update() { 107 | // sets the initial value (no interpolation) 108 | for (const key in this.renderedStyles ) { 109 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 110 | } 111 | // apply changes/styles 112 | this.layout(); 113 | } 114 | getSize() { 115 | const rect = this.DOM.el.getBoundingClientRect(); 116 | this.props = { 117 | // item's height 118 | height: rect.height, 119 | // offset top relative to the document 120 | top: docScroll + rect.top 121 | } 122 | } 123 | initEvents() { 124 | window.addEventListener('resize', () => this.resize()); 125 | } 126 | resize() { 127 | // gets the item's height and top (relative to the document) 128 | this.getSize(); 129 | // on resize reset sizes and update styles 130 | this.update(); 131 | } 132 | render() { 133 | // update the current and interpolated values 134 | for (const key in this.renderedStyles ) { 135 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 136 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 137 | } 138 | 139 | // and apply changes 140 | this.layout(); 141 | } 142 | layout() { 143 | // translates the image 144 | this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`; 145 | // rotate the image wrapper 146 | this.DOM.imageWrapper.style.transform = `rotate3d(1,${this.ry},${this.rz},${this.renderedStyles.itemRotation.previous}deg)`; 147 | } 148 | } 149 | 150 | // SmoothScroll 151 | class SmoothScroll { 152 | constructor() { 153 | // the
element 154 | this.DOM = {main: document.querySelector('main')}; 155 | // the scrollable element 156 | // we translate this element when scrolling (y-axis) 157 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 158 | // the items on the page 159 | this.items = []; 160 | this.DOM.content = this.DOM.main.querySelector('.content'); 161 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 162 | // here we define which property will change as we scroll the page 163 | // in this case we will be translating on the y-axis 164 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 165 | this.renderedStyles = { 166 | translationY: { 167 | // interpolated value 168 | previous: 0, 169 | // current value 170 | current: 0, 171 | // amount to interpolate 172 | ease: 0.1, 173 | // current value setter 174 | // in this case the value of the translation will be the same like the document scroll 175 | setValue: () => docScroll 176 | } 177 | }; 178 | // set the body's height 179 | this.setSize(); 180 | // set the initial values 181 | this.update(); 182 | // the
element's style needs to be modified 183 | this.style(); 184 | // init/bind events 185 | this.initEvents(); 186 | // start the render loop 187 | requestAnimationFrame(() => this.render()); 188 | } 189 | update() { 190 | // sets the initial value (no interpolation) - translate the scroll value 191 | for (const key in this.renderedStyles ) { 192 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 193 | } 194 | // translate the scrollable element 195 | this.layout(); 196 | } 197 | layout() { 198 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 199 | } 200 | setSize() { 201 | // set the heigh of the body in order to keep the scrollbar on the page 202 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 203 | } 204 | style() { 205 | // the
needs to "stick" to the screen and not scroll 206 | // for that we set it to position fixed and overflow hidden 207 | this.DOM.main.style.position = 'fixed'; 208 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 209 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 210 | this.DOM.main.style.overflow = 'hidden'; 211 | } 212 | initEvents() { 213 | // on resize reset the body's height 214 | window.addEventListener('resize', () => this.setSize()); 215 | } 216 | render() { 217 | // Get scrolling speed 218 | // Update lastScroll 219 | scrollingSpeed = Math.abs(docScroll - lastScroll); 220 | lastScroll = docScroll; 221 | 222 | // update the current and interpolated values 223 | for (const key in this.renderedStyles ) { 224 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 225 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 226 | } 227 | // and translate the scrollable element 228 | this.layout(); 229 | 230 | // for every item 231 | for (const item of this.items) { 232 | // if the item is inside the viewport call it's render function 233 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 234 | if ( item.isVisible ) { 235 | if ( item.insideViewport ) { 236 | item.render(); 237 | } 238 | else { 239 | item.insideViewport = true; 240 | item.update(); 241 | } 242 | } 243 | else { 244 | item.insideViewport = false; 245 | } 246 | } 247 | 248 | // loop.. 249 | requestAnimationFrame(() => this.render()); 250 | } 251 | } 252 | 253 | /***********************************/ 254 | /********** Preload stuff **********/ 255 | 256 | // Preload images 257 | const preloadImages = () => { 258 | return new Promise((resolve, reject) => { 259 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 260 | }); 261 | }; 262 | 263 | // And then.. 264 | preloadImages().then(() => { 265 | // Remove the loader 266 | document.body.classList.remove('loading'); 267 | // Get the scroll position and update the lastScroll variable 268 | getPageYScroll(); 269 | lastScroll = docScroll; 270 | // Initialize the Smooth Scrolling 271 | new SmoothScroll(); 272 | }); 273 | } -------------------------------------------------------------------------------- /js/demo5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo5.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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | // the inner image 47 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 48 | this.DOM.imageWrapper = this.DOM.image.parentNode; 49 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 50 | this.renderedStyles = { 51 | // here we define which property will change as we scroll the page and the item is inside the viewport 52 | // in this case we will be: 53 | // - translating/rotating the title 54 | // - rotating the item 55 | // we interpolate between the previous and current value to achieve a smooth effect 56 | titleTranslationY: { 57 | previous: 0, 58 | current: 0, 59 | ease: 0.08, 60 | fromValue: Number(MathUtils.getRandomFloat(50,1000)), 61 | setValue: () => { 62 | const fromValue = this.renderedStyles.titleTranslationY.fromValue; 63 | const toValue = -1*fromValue; 64 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 65 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 66 | } 67 | }, 68 | titleRotation: { 69 | previous: 0, 70 | current: 0, 71 | ease: 0.1, 72 | fromValue: Number(MathUtils.getRandomFloat(-75,75)), 73 | setValue: () => { 74 | const fromValue = this.renderedStyles.titleRotation.fromValue; 75 | const toValue = fromValue+15; 76 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 77 | return Math.min(Math.max(val, fromValue), toValue); 78 | } 79 | }, 80 | itemRotation: { 81 | previous: 0, 82 | current: 0, 83 | ease: 0.1, 84 | fromValue: Number(MathUtils.getRandomFloat(-75,75)), 85 | setValue: () => { 86 | const fromValue = this.renderedStyles.itemRotation.fromValue; 87 | const toValue = fromValue+15; 88 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 89 | return Math.min(Math.max(val, fromValue), toValue); 90 | } 91 | } 92 | }; 93 | // gets the item's height and top (relative to the document) 94 | this.getSize(); 95 | // set the initial values 96 | this.update(); 97 | // use the IntersectionObserver API to check when the element is inside the viewport 98 | // only then the element styles will be updated 99 | this.observer = new IntersectionObserver((entries) => { 100 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 101 | }); 102 | this.observer.observe(this.DOM.image); 103 | // init/bind events 104 | this.initEvents(); 105 | } 106 | update() { 107 | // sets the initial value (no interpolation) 108 | for (const key in this.renderedStyles ) { 109 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 110 | } 111 | // apply changes/styles 112 | this.layout(); 113 | } 114 | getSize() { 115 | const rect = this.DOM.el.getBoundingClientRect(); 116 | this.props = { 117 | // item's height 118 | height: rect.height, 119 | // offset top relative to the document 120 | top: docScroll + rect.top 121 | } 122 | } 123 | initEvents() { 124 | window.addEventListener('resize', () => this.resize()); 125 | } 126 | resize() { 127 | // gets the item's height and top (relative to the document) 128 | this.getSize(); 129 | // on resize reset sizes and update styles 130 | this.update(); 131 | } 132 | render() { 133 | // update the current and interpolated values 134 | for (const key in this.renderedStyles ) { 135 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 136 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 137 | } 138 | 139 | // and apply changes 140 | this.layout(); 141 | } 142 | layout() { 143 | // translate/rotate the title 144 | this.DOM.title.style.transform = `rotate3d(0,0,1,${this.renderedStyles.titleRotation.previous}deg) translate3d(0,${this.renderedStyles.titleTranslationY.previous}px,0)`; 145 | // rotate the image wrapper 146 | this.DOM.imageWrapper.style.transform = `rotate3d(0,0,1,${this.renderedStyles.itemRotation.previous}deg)`; 147 | } 148 | } 149 | 150 | // SmoothScroll 151 | class SmoothScroll { 152 | constructor() { 153 | // the
element 154 | this.DOM = {main: document.querySelector('main')}; 155 | // the scrollable element 156 | // we translate this element when scrolling (y-axis) 157 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 158 | // the items on the page 159 | this.items = []; 160 | this.DOM.content = this.DOM.main.querySelector('.content'); 161 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 162 | // here we define which property will change as we scroll the page 163 | // in this case we will be translating on the y-axis 164 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 165 | this.renderedStyles = { 166 | translationY: { 167 | // interpolated value 168 | previous: 0, 169 | // current value 170 | current: 0, 171 | // amount to interpolate 172 | ease: 0.1, 173 | // current value setter 174 | // in this case the value of the translation will be the same like the document scroll 175 | setValue: () => docScroll 176 | } 177 | }; 178 | // set the body's height 179 | this.setSize(); 180 | // set the initial values 181 | this.update(); 182 | // the
element's style needs to be modified 183 | this.style(); 184 | // init/bind events 185 | this.initEvents(); 186 | // start the render loop 187 | requestAnimationFrame(() => this.render()); 188 | } 189 | update() { 190 | // sets the initial value (no interpolation) - translate the scroll value 191 | for (const key in this.renderedStyles ) { 192 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 193 | } 194 | // translate the scrollable element 195 | this.layout(); 196 | } 197 | layout() { 198 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 199 | } 200 | setSize() { 201 | // set the heigh of the body in order to keep the scrollbar on the page 202 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 203 | } 204 | style() { 205 | // the
needs to "stick" to the screen and not scroll 206 | // for that we set it to position fixed and overflow hidden 207 | this.DOM.main.style.position = 'fixed'; 208 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 209 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 210 | this.DOM.main.style.overflow = 'hidden'; 211 | } 212 | initEvents() { 213 | // on resize reset the body's height 214 | window.addEventListener('resize', () => this.setSize()); 215 | } 216 | render() { 217 | // Get scrolling speed 218 | // Update lastScroll 219 | scrollingSpeed = Math.abs(docScroll - lastScroll); 220 | lastScroll = docScroll; 221 | 222 | // update the current and interpolated values 223 | for (const key in this.renderedStyles ) { 224 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 225 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 226 | } 227 | // and translate the scrollable element 228 | this.layout(); 229 | 230 | // for every item 231 | for (const item of this.items) { 232 | // if the item is inside the viewport call it's render function 233 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 234 | if ( item.isVisible ) { 235 | if ( item.insideViewport ) { 236 | item.render(); 237 | } 238 | else { 239 | item.insideViewport = true; 240 | item.update(); 241 | } 242 | } 243 | else { 244 | item.insideViewport = false; 245 | } 246 | } 247 | 248 | // loop.. 249 | requestAnimationFrame(() => this.render()); 250 | } 251 | } 252 | 253 | /***********************************/ 254 | /********** Preload stuff **********/ 255 | 256 | // Preload images 257 | const preloadImages = () => { 258 | return new Promise((resolve, reject) => { 259 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 260 | }); 261 | }; 262 | 263 | // And then.. 264 | preloadImages().then(() => { 265 | // Remove the loader 266 | document.body.classList.remove('loading'); 267 | // Get the scroll position and update the lastScroll variable 268 | getPageYScroll(); 269 | lastScroll = docScroll; 270 | // Initialize the Smooth Scrolling 271 | new SmoothScroll(); 272 | }); 273 | } -------------------------------------------------------------------------------- /js/demo4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo4.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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | // the inner image 47 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 48 | this.DOM.imageWrapper = this.DOM.image.parentNode; 49 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 50 | this.renderedStyles = { 51 | // here we define which property will change as we scroll the page and the item is inside the viewport 52 | // in this case we will be: 53 | // - translating the inner image 54 | // - rotating the item 55 | // - translating the title 56 | // we interpolate between the previous and current value to achieve a smooth effect 57 | innerTranslationY: { 58 | // interpolated value 59 | previous: 0, 60 | // current value 61 | current: 0, 62 | // amount to interpolate 63 | ease: 0.1, 64 | // current value setter 65 | setValue: () => { 66 | // the maximum value to translate the image is set in a CSS variable (--overflow) 67 | const toValue = parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10); 68 | const fromValue = -1 * toValue; 69 | return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue), toValue), fromValue); 70 | } 71 | }, 72 | titleTranslationY: { 73 | previous: 0, 74 | current: 0, 75 | ease: 0.1, 76 | fromValue: Number(MathUtils.getRandomFloat(30,400)), 77 | setValue: () => { 78 | const fromValue = this.renderedStyles.titleTranslationY.fromValue; 79 | const toValue = -1*fromValue; 80 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 81 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 82 | } 83 | }, 84 | itemRotation: { 85 | previous: 0, 86 | current: 0, 87 | ease: 0.1, 88 | fromValue: [Number(MathUtils.getRandomFloat(-25,-15)),Number(MathUtils.getRandomFloat(15,25))][Math.round(MathUtils.getRandomFloat(0,1))], 89 | setValue: () => { 90 | const fromValue = this.renderedStyles.itemRotation.fromValue; 91 | const toValue = -1*fromValue; 92 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 93 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 94 | } 95 | } 96 | }; 97 | // gets the item's height and top (relative to the document) 98 | this.getSize(); 99 | // set the initial values 100 | this.update(); 101 | // use the IntersectionObserver API to check when the element is inside the viewport 102 | // only then the element styles will be updated 103 | this.observer = new IntersectionObserver((entries) => { 104 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 105 | }); 106 | this.observer.observe(this.DOM.image); 107 | // init/bind events 108 | this.initEvents(); 109 | } 110 | update() { 111 | // sets the initial value (no interpolation) 112 | for (const key in this.renderedStyles ) { 113 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 114 | } 115 | // apply changes/styles 116 | this.layout(); 117 | } 118 | getSize() { 119 | const rect = this.DOM.el.getBoundingClientRect(); 120 | this.props = { 121 | // item's height 122 | height: rect.height, 123 | // offset top relative to the document 124 | top: docScroll + rect.top 125 | } 126 | } 127 | initEvents() { 128 | window.addEventListener('resize', () => this.resize()); 129 | } 130 | resize() { 131 | // gets the item's height and top (relative to the document) 132 | this.getSize(); 133 | // on resize reset sizes and update styles 134 | this.update(); 135 | } 136 | render() { 137 | // update the current and interpolated values 138 | for (const key in this.renderedStyles ) { 139 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 140 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 141 | } 142 | 143 | // and apply changes 144 | this.layout(); 145 | } 146 | layout() { 147 | // translates the image 148 | this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`; 149 | // translate the title 150 | this.DOM.title.style.transform = `translate3d(0,${this.renderedStyles.titleTranslationY.previous}px,0)`; 151 | // rotate the item 152 | this.DOM.el.style.transform = `rotate3d(0,0,1,${this.renderedStyles.itemRotation.previous}deg)`; 153 | } 154 | } 155 | 156 | // SmoothScroll 157 | class SmoothScroll { 158 | constructor() { 159 | // the
element 160 | this.DOM = {main: document.querySelector('main')}; 161 | // the scrollable element 162 | // we translate this element when scrolling (y-axis) 163 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 164 | // the items on the page 165 | this.items = []; 166 | this.DOM.content = this.DOM.main.querySelector('.content'); 167 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 168 | // here we define which property will change as we scroll the page 169 | // in this case we will be translating on the y-axis 170 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 171 | this.renderedStyles = { 172 | translationY: { 173 | // interpolated value 174 | previous: 0, 175 | // current value 176 | current: 0, 177 | // amount to interpolate 178 | ease: 0.1, 179 | // current value setter 180 | // in this case the value of the translation will be the same like the document scroll 181 | setValue: () => docScroll 182 | } 183 | }; 184 | // set the body's height 185 | this.setSize(); 186 | // set the initial values 187 | this.update(); 188 | // the
element's style needs to be modified 189 | this.style(); 190 | // init/bind events 191 | this.initEvents(); 192 | // start the render loop 193 | requestAnimationFrame(() => this.render()); 194 | } 195 | update() { 196 | // sets the initial value (no interpolation) - translate the scroll value 197 | for (const key in this.renderedStyles ) { 198 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 199 | } 200 | // translate the scrollable element 201 | this.layout(); 202 | } 203 | layout() { 204 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 205 | } 206 | setSize() { 207 | // set the heigh of the body in order to keep the scrollbar on the page 208 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 209 | } 210 | style() { 211 | // the
needs to "stick" to the screen and not scroll 212 | // for that we set it to position fixed and overflow hidden 213 | this.DOM.main.style.position = 'fixed'; 214 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 215 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 216 | this.DOM.main.style.overflow = 'hidden'; 217 | } 218 | initEvents() { 219 | // on resize reset the body's height 220 | window.addEventListener('resize', () => this.setSize()); 221 | } 222 | render() { 223 | // Get scrolling speed 224 | // Update lastScroll 225 | scrollingSpeed = Math.abs(docScroll - lastScroll); 226 | lastScroll = docScroll; 227 | 228 | // update the current and interpolated values 229 | for (const key in this.renderedStyles ) { 230 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 231 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 232 | } 233 | // and translate the scrollable element 234 | this.layout(); 235 | 236 | // for every item 237 | for (const item of this.items) { 238 | // if the item is inside the viewport call it's render function 239 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 240 | if ( item.isVisible ) { 241 | if ( item.insideViewport ) { 242 | item.render(); 243 | } 244 | else { 245 | item.insideViewport = true; 246 | item.update(); 247 | } 248 | } 249 | else { 250 | item.insideViewport = false; 251 | } 252 | } 253 | 254 | // loop.. 255 | requestAnimationFrame(() => this.render()); 256 | } 257 | } 258 | 259 | /***********************************/ 260 | /********** Preload stuff **********/ 261 | 262 | // Preload images 263 | const preloadImages = () => { 264 | return new Promise((resolve, reject) => { 265 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 266 | }); 267 | }; 268 | 269 | // And then.. 270 | preloadImages().then(() => { 271 | // Remove the loader 272 | document.body.classList.remove('loading'); 273 | // Get the scroll position and update the lastScroll variable 274 | getPageYScroll(); 275 | lastScroll = docScroll; 276 | // Initialize the Smooth Scrolling 277 | new SmoothScroll(); 278 | }); 279 | } -------------------------------------------------------------------------------- /js/demo2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo2.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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | // the inner image 47 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 48 | this.DOM.imageWrapper = this.DOM.image.parentNode; 49 | this.DOM.imageWrapper.style.transformOrigin = '50% 100%'; 50 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 51 | this.renderedStyles = { 52 | // here we define which property will change as we scroll the page and the item is inside the viewport 53 | // in this case we will be: 54 | // - translate the inner image 55 | // - translating/rotating the item's title 56 | // - scaling the image wrapper 57 | // we interpolate between the previous and current value to achieve a smooth effect 58 | innerTranslationY: { 59 | // interpolated value 60 | previous: 0, 61 | // current value 62 | current: 0, 63 | // amount to interpolate 64 | ease: 0.1, 65 | // current value setter 66 | setValue: () => { 67 | // the maximum value to translate the image is set in a CSS variable (--overflow) 68 | const toValue = parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10); 69 | const fromValue = -1 * toValue; 70 | return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue), toValue), fromValue); 71 | } 72 | }, 73 | titleTranslationY: { 74 | previous: 0, 75 | current: 0, 76 | ease: 0.1, 77 | fromValue: Number(MathUtils.getRandomFloat(30,400)), 78 | setValue: () => { 79 | const fromValue = this.renderedStyles.titleTranslationY.fromValue; 80 | const toValue = -1*fromValue; 81 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 82 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 83 | } 84 | }, 85 | itemRotation: { 86 | previous: 0, 87 | current: 0, 88 | ease: 0.1, 89 | fromValue: Number(MathUtils.getRandomFloat(-10,10)), 90 | //fromValue: -45, 91 | setValue: () => { 92 | const fromValue = this.renderedStyles.itemRotation.fromValue; 93 | const toValue = 0; 94 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, winsize.height/2 - this.props.height/2, fromValue, toValue); 95 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 96 | } 97 | }, 98 | imageScaleX: { 99 | previous: 0, 100 | current: 0, 101 | ease: 0.1, 102 | setValue: () => { 103 | const fromValue = 1; 104 | const toValue = 0.7; 105 | const val = MathUtils.map(this.props.top - docScroll, winsize.height/6, -1 * this.props.height, fromValue, toValue); 106 | return Math.max(Math.min(val, fromValue), toValue); 107 | } 108 | }, 109 | imageScaleY: { 110 | previous: 0, 111 | current: 0, 112 | ease: 0.1, 113 | setValue: () => { 114 | const fromValue = 1; 115 | const toValue = 1.5; 116 | const val = MathUtils.map(this.props.top - docScroll, winsize.height/6, -1 * this.props.height, fromValue, toValue); 117 | return Math.min(Math.max(val, fromValue), toValue); 118 | } 119 | } 120 | }; 121 | // gets the item's height and top (relative to the document) 122 | this.getSize(); 123 | // set the initial values 124 | this.update(); 125 | // use the IntersectionObserver API to check when the element is inside the viewport 126 | // only then the element styles will be updated 127 | this.observer = new IntersectionObserver((entries) => { 128 | entries.forEach(entry => this.isVisible = entry.intersectionRatio > 0); 129 | }); 130 | this.observer.observe(this.DOM.el); 131 | // init/bind events 132 | this.initEvents(); 133 | } 134 | update() { 135 | // sets the initial value (no interpolation) 136 | for (const key in this.renderedStyles ) { 137 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 138 | } 139 | // apply changes/styles 140 | this.layout(); 141 | } 142 | getSize() { 143 | const rect = this.DOM.el.getBoundingClientRect(); 144 | this.props = { 145 | // item's height 146 | height: rect.height, 147 | // offset top relative to the document 148 | top: docScroll + rect.top 149 | } 150 | } 151 | initEvents() { 152 | window.addEventListener('resize', () => this.resize()); 153 | } 154 | resize() { 155 | // gets the item's height and top (relative to the document) 156 | this.getSize(); 157 | // on resize reset sizes and update styles 158 | this.update(); 159 | } 160 | render() { 161 | // update the current and interpolated values 162 | for (const key in this.renderedStyles ) { 163 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 164 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 165 | } 166 | 167 | // and apply changes 168 | this.layout(); 169 | } 170 | layout() { 171 | // translates the image 172 | this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`; 173 | this.DOM.imageWrapper.style.transform = `scale3d(${this.renderedStyles.imageScaleX.previous},${this.renderedStyles.imageScaleY.previous},1)`; 174 | // translate the title 175 | this.DOM.title.style.transform = `translate3d(0,${this.renderedStyles.titleTranslationY.previous}px,0)`; 176 | // rotate the item 177 | this.DOM.el.style.transform = `rotate3d(0,0,1,${this.renderedStyles.itemRotation.previous}deg)`; 178 | } 179 | } 180 | 181 | // SmoothScroll 182 | class SmoothScroll { 183 | constructor() { 184 | // the
element 185 | this.DOM = {main: document.querySelector('main')}; 186 | // the scrollable element 187 | // we translate this element when scrolling (y-axis) 188 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 189 | // the items on the page 190 | this.items = []; 191 | this.DOM.content = this.DOM.main.querySelector('.content'); 192 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 193 | // here we define which property will change as we scroll the page 194 | // in this case we will be translating on the y-axis 195 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 196 | this.renderedStyles = { 197 | translationY: { 198 | // interpolated value 199 | previous: 0, 200 | // current value 201 | current: 0, 202 | // amount to interpolate 203 | ease: 0.1, 204 | // current value setter 205 | // in this case the value of the translation will be the same like the document scroll 206 | setValue: () => docScroll 207 | } 208 | }; 209 | // set the body's height 210 | this.setSize(); 211 | // set the initial values 212 | this.update(); 213 | // the
element's style needs to be modified 214 | this.style(); 215 | // init/bind events 216 | this.initEvents(); 217 | // start the render loop 218 | requestAnimationFrame(() => this.render()); 219 | } 220 | update() { 221 | // sets the initial value (no interpolation) - translate the scroll value 222 | for (const key in this.renderedStyles ) { 223 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 224 | } 225 | // translate the scrollable element 226 | this.layout(); 227 | } 228 | layout() { 229 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0)`; 230 | } 231 | setSize() { 232 | // set the heigh of the body in order to keep the scrollbar on the page 233 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 234 | } 235 | style() { 236 | // the
needs to "stick" to the screen and not scroll 237 | // for that we set it to position fixed and overflow hidden 238 | this.DOM.main.style.position = 'fixed'; 239 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 240 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 241 | this.DOM.main.style.overflow = 'hidden'; 242 | } 243 | initEvents() { 244 | // on resize reset the body's height 245 | window.addEventListener('resize', () => this.setSize()); 246 | } 247 | render() { 248 | // Get scrolling speed 249 | // Update lastScroll 250 | scrollingSpeed = Math.abs(docScroll - lastScroll); 251 | lastScroll = docScroll; 252 | 253 | // update the current and interpolated values 254 | for (const key in this.renderedStyles ) { 255 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 256 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 257 | } 258 | // and translate the scrollable element 259 | this.layout(); 260 | 261 | // for every item 262 | for (const item of this.items) { 263 | // if the item is inside the viewport call it's render function 264 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 265 | if ( item.isVisible ) { 266 | if ( item.insideViewport ) { 267 | item.render(); 268 | } 269 | else { 270 | item.insideViewport = true; 271 | item.update(); 272 | } 273 | } 274 | else { 275 | item.insideViewport = false; 276 | } 277 | } 278 | 279 | // loop.. 280 | requestAnimationFrame(() => this.render()); 281 | } 282 | } 283 | 284 | /***********************************/ 285 | /********** Preload stuff **********/ 286 | 287 | // Preload images 288 | const preloadImages = () => { 289 | return new Promise((resolve, reject) => { 290 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 291 | }); 292 | }; 293 | 294 | // And then.. 295 | preloadImages().then(() => { 296 | // Remove the loader 297 | document.body.classList.remove('loading'); 298 | // Get the scroll position and update the lastScroll variable 299 | getPageYScroll(); 300 | lastScroll = docScroll; 301 | // Initialize the Smooth Scrolling 302 | new SmoothScroll(); 303 | }); 304 | } -------------------------------------------------------------------------------- /js/demo6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * demo6.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 | // Random float 19 | getRandomFloat: (min, max) => (Math.random() * (max - min) + min).toFixed(2) 20 | }; 21 | 22 | // body element 23 | const body = document.body; 24 | 25 | // calculate the viewport size 26 | let winsize; 27 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 28 | calcWinsize(); 29 | // and recalculate on resize 30 | window.addEventListener('resize', calcWinsize); 31 | 32 | // scroll position 33 | let docScroll; 34 | // for scroll speed calculation 35 | let lastScroll; 36 | let scrollingSpeed = 0; 37 | // scroll position update function 38 | const getPageYScroll = () => docScroll = window.pageYOffset || document.documentElement.scrollTop; 39 | window.addEventListener('scroll', getPageYScroll); 40 | 41 | // Item 42 | class Item { 43 | constructor(el) { 44 | // the .item element 45 | this.DOM = {el: el}; 46 | // the inner image 47 | this.DOM.image = this.DOM.el.querySelector('.content__item-img'); 48 | this.DOM.imageWrapper = this.DOM.image.parentNode; 49 | this.DOM.title = this.DOM.el.querySelector('.content__item-title'); 50 | this.DOM.deco = this.DOM.el.querySelector('.content__item-decobar'); 51 | this.renderedStyles = { 52 | // here we define which property will change as we scroll the page and the item is inside the viewport 53 | // in this case we will be: 54 | // - translating the inner image 55 | // - translating the item's title, rotating and scaling it down 56 | // - translating the decobar 57 | // we interpolate between the previous and current value to achieve a smooth effect 58 | innerTranslationY: { 59 | // interpolated value 60 | previous: 0, 61 | // current value 62 | current: 0, 63 | // amount to interpolate 64 | ease: 0.1, 65 | // current value setter 66 | setValue: () => { 67 | // the maximum value to translate the image is set in a CSS variable (--overflow) 68 | const toValue = parseInt(getComputedStyle(this.DOM.image).getPropertyValue('--overflow'), 10); 69 | const fromValue = -1 * toValue; 70 | return Math.max(Math.min(MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue), toValue), fromValue); 71 | } 72 | }, 73 | titleTranslationY: { 74 | previous: 0, 75 | current: 0, 76 | ease: 0.1, 77 | fromValue: Number(MathUtils.getRandomFloat(30,400)), 78 | setValue: () => { 79 | const fromValue = this.renderedStyles.titleTranslationY.fromValue; 80 | const toValue = -1*fromValue; 81 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 82 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 83 | } 84 | }, 85 | titleScale: { 86 | previous: 0, 87 | current: 0, 88 | ease: 0.1, 89 | setValue: () => { 90 | const fromValue = 1; 91 | const toValue = 0.6; 92 | const val = MathUtils.map(this.props.top - docScroll, winsize.height/2, -1 * this.props.height, fromValue, toValue); 93 | return fromValue < 0 ? Math.min(Math.max(val, fromValue), toValue) : Math.max(Math.min(val, fromValue), toValue); 94 | } 95 | }, 96 | titleRotation: { 97 | previous: 0, 98 | current: 0, 99 | ease: 0.1, 100 | setValue: () => { 101 | const fromValue = 0; 102 | const toValue = 10; 103 | const val = MathUtils.map(this.props.top - docScroll, winsize.height/2, -1 * this.props.height, fromValue, toValue); 104 | return Math.max(Math.min(val, toValue), fromValue); 105 | } 106 | }, 107 | decoTranslationY: { 108 | previous: 0, 109 | current: 0, 110 | ease: 0.1, 111 | setValue: () => { 112 | const fromValue = -600; 113 | const toValue = 600; 114 | const val = MathUtils.map(this.props.top - docScroll, winsize.height, -1 * this.props.height, fromValue, toValue); 115 | return Math.min(Math.max(val, fromValue), toValue); 116 | } 117 | } 118 | }; 119 | // gets the item's height and top (relative to the document) 120 | this.getSize(); 121 | // set the initial values 122 | this.update(); 123 | // use the IntersectionObserver API to check when the element is inside the viewport 124 | // only then the element styles will be updated 125 | this.observer = new IntersectionObserver((entries) => { 126 | entries.forEach(entry => { 127 | this.isVisible = entry.intersectionRatio > 0; 128 | 129 | if ( entry.intersectionRatio > 0.5 ) { 130 | TweenMax.set(this.DOM.deco, { 131 | transformOrigin: '0% 50%' 132 | }); 133 | TweenMax.to(this.DOM.deco, 0.9, { 134 | ease: Expo.easeOut, 135 | startAt: {opacity: 1, scaleX: 0}, 136 | scaleX: 1 137 | }); 138 | } 139 | else { 140 | TweenMax.set(this.DOM.deco, { 141 | transformOrigin: '100% 50%' 142 | }); 143 | TweenMax.to(this.DOM.deco, 0.4, { 144 | ease: Expo.easeOut, 145 | scaleX: 0 146 | }); 147 | } 148 | }); 149 | }, { 150 | threshold: [0,0.5] 151 | }); 152 | this.observer.observe(this.DOM.el); 153 | // init/bind events 154 | this.initEvents(); 155 | } 156 | update() { 157 | // sets the initial value (no interpolation) 158 | for (const key in this.renderedStyles ) { 159 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 160 | } 161 | // apply changes/styles 162 | this.layout(); 163 | } 164 | getSize() { 165 | const rect = this.DOM.el.getBoundingClientRect(); 166 | this.props = { 167 | // item's height 168 | height: rect.height, 169 | // offset top relative to the document 170 | top: docScroll + rect.top 171 | } 172 | } 173 | initEvents() { 174 | window.addEventListener('resize', () => this.resize()); 175 | } 176 | resize() { 177 | // gets the item's height and top (relative to the document) 178 | this.getSize(); 179 | // on resize reset sizes and update styles 180 | this.update(); 181 | } 182 | render() { 183 | // update the current and interpolated values 184 | for (const key in this.renderedStyles ) { 185 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 186 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 187 | } 188 | 189 | // and apply changes 190 | this.layout(); 191 | } 192 | layout() { 193 | // translate the image 194 | this.DOM.image.style.transform = `translate3d(0,${this.renderedStyles.innerTranslationY.previous}px,0)`; 195 | // translate/scale/rotate the title 196 | this.DOM.title.style.transform = `scale3d(${this.renderedStyles.titleScale.previous},${this.renderedStyles.titleScale.previous},1) translate3d(0,${this.renderedStyles.titleTranslationY.previous}px,0) rotate3d(0,0,1,${this.renderedStyles.titleRotation.previous}deg)`; 197 | // translate the deco 198 | TweenMax.set(this.DOM.deco, {y: this.renderedStyles.decoTranslationY.previous}); 199 | } 200 | } 201 | 202 | // SmoothScroll 203 | class SmoothScroll { 204 | constructor() { 205 | // the
element 206 | this.DOM = {main: document.querySelector('main')}; 207 | // the scrollable element 208 | // we translate this element when scrolling (y-axis) 209 | this.DOM.scrollable = this.DOM.main.querySelector('div[data-scroll]'); 210 | // the items on the page 211 | this.items = []; 212 | this.DOM.content = this.DOM.main.querySelector('.content'); 213 | [...this.DOM.content.querySelectorAll('.content__item')].forEach(item => this.items.push(new Item(item))); 214 | // here we define which property will change as we scroll the page 215 | // in this case we will be translating on the y-axis 216 | // we interpolate between the previous and current value to achieve the smooth scrolling effect 217 | this.renderedStyles = { 218 | translationY: { 219 | // interpolated value 220 | previous: 0, 221 | // current value 222 | current: 0, 223 | // amount to interpolate 224 | ease: 0.1, 225 | // current value setter 226 | // in this case the value of the translation will be the same like the document scroll 227 | setValue: () => docScroll 228 | }, 229 | skew: { 230 | previous: 0, 231 | current: 0, 232 | ease: 0.1, 233 | setValue: () => { 234 | const toValue = 30; 235 | const fromValue = 0; 236 | const val = Math.max(Math.min(MathUtils.map(scrollingSpeed, 20, 100, fromValue, toValue), toValue), fromValue) 237 | return this.renderedStyles.translationY.previous < docScroll ? val : -1*val; 238 | } 239 | } 240 | }; 241 | // set the body's height 242 | this.setSize(); 243 | // set the initial values 244 | this.update(); 245 | // the
element's style needs to be modified 246 | this.style(); 247 | // init/bind events 248 | this.initEvents(); 249 | // start the render loop 250 | requestAnimationFrame(() => this.render()); 251 | } 252 | update() { 253 | // sets the initial value (no interpolation) - translate the scroll value 254 | for (const key in this.renderedStyles ) { 255 | this.renderedStyles[key].current = this.renderedStyles[key].previous = this.renderedStyles[key].setValue(); 256 | } 257 | // translate the scrollable element 258 | this.layout(); 259 | } 260 | layout() { 261 | this.DOM.scrollable.style.transform = `translate3d(0,${-1*this.renderedStyles.translationY.previous}px,0) skewY(${this.renderedStyles.skew.previous}deg)`; 262 | } 263 | setSize() { 264 | // set the heigh of the body in order to keep the scrollbar on the page 265 | body.style.height = `${this.DOM.scrollable.scrollHeight}px`; 266 | } 267 | style() { 268 | // the
needs to "stick" to the screen and not scroll 269 | // for that we set it to position fixed and overflow hidden 270 | this.DOM.main.style.position = 'fixed'; 271 | this.DOM.main.style.width = this.DOM.main.style.height = '100%'; 272 | this.DOM.main.style.top = this.DOM.main.style.left = 0; 273 | this.DOM.main.style.overflow = 'hidden'; 274 | } 275 | initEvents() { 276 | // on resize reset the body's height 277 | window.addEventListener('resize', () => this.setSize()); 278 | } 279 | render() { 280 | // Get scrolling speed 281 | // Update lastScroll 282 | scrollingSpeed = Math.abs(docScroll - lastScroll); 283 | lastScroll = docScroll; 284 | 285 | // update the current and interpolated values 286 | for (const key in this.renderedStyles ) { 287 | this.renderedStyles[key].current = this.renderedStyles[key].setValue(); 288 | this.renderedStyles[key].previous = MathUtils.lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].ease); 289 | } 290 | // and translate the scrollable element 291 | this.layout(); 292 | 293 | // for every item 294 | for (const item of this.items) { 295 | // if the item is inside the viewport call it's render function 296 | // this will update item's styles, based on the document scroll value and the item's position on the viewport 297 | if ( item.isVisible ) { 298 | if ( item.insideViewport ) { 299 | item.render(); 300 | } 301 | else { 302 | item.insideViewport = true; 303 | item.update(); 304 | } 305 | } 306 | else { 307 | item.insideViewport = false; 308 | } 309 | } 310 | 311 | // loop.. 312 | requestAnimationFrame(() => this.render()); 313 | } 314 | } 315 | 316 | /***********************************/ 317 | /********** Preload stuff **********/ 318 | 319 | // Preload images 320 | const preloadImages = () => { 321 | return new Promise((resolve, reject) => { 322 | imagesLoaded(document.querySelectorAll('.content__item-img'), {background: true}, resolve); 323 | }); 324 | }; 325 | 326 | // And then.. 327 | preloadImages().then(() => { 328 | // Remove the loader 329 | document.body.classList.remove('loading'); 330 | // Get the scroll position and update the lastScroll variable 331 | getPageYScroll(); 332 | lastScroll = docScroll; 333 | // Initialize the Smooth Scrolling 334 | new SmoothScroll(); 335 | }); 336 | } --------------------------------------------------------------------------------