├── .gitignore ├── README.md ├── css └── base.css ├── favicon.ico ├── img ├── 1.jpg ├── 10.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg ├── 9.jpg └── large.jpg ├── index.html ├── index2.html └── js ├── blotter.min.js ├── demo.js ├── demo2.js ├── imagesloaded.pkgd.min.js └── materials ├── channelSplitMaterial.js ├── fliesMaterial.js ├── liquidDistortMaterial.js ├── rollingDistortMaterial.js └── slidingDoorMaterial.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Text Distortion Effects using Blotter.js 2 | 3 | Some text distortion experiments using the [Blotter.js](https://blotter.js.org/) library. 4 | 5 | ![Text Distortion Effects](https://tympanus.net/codrops/wp-content/uploads/2019/02/TextDistortionEffects_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=38200) 8 | 9 | [Demo](http://tympanus.net/Development/TextDistortionEffects/) 10 | 11 | ## Credits 12 | 13 | - [Blotter.js](https://blotter.js.org/) 14 | - Images from [Unsplash.com](https://unsplash.com/) 15 | - [imagesLoaded](https://imagesloaded.desandro.com/) by Dave DeSandro 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 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} 2 | *, 3 | *::after, 4 | *::before { 5 | box-sizing: border-box; 6 | } 7 | 8 | :root { 9 | font-size: 16px; 10 | } 11 | 12 | body { 13 | --color-text: #99a3bb; 14 | --color-bg: #2b313e; 15 | --color-link: #59647e; 16 | --color-link-hover: #cc8678; 17 | --color-overlay: #451a11; 18 | --color-tagline: #c69f64; 19 | color: var(--color-text); 20 | background-color: var(--color-bg); 21 | font-family: Futura, 'IBM Plex Sans', Arial, sans-serif; 22 | font-weight: 500; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .demo-1::before { 28 | content: '\00D7'; 29 | font-size: 300vw; 30 | color: rgba(0,0,0,0.05); 31 | position: fixed; 32 | top: 0vh; 33 | left: -50%; 34 | line-height: 0.2; 35 | font-weight: normal; 36 | } 37 | 38 | .demo-2 { 39 | --color-text: #000; 40 | --color-bg: #a99894; 41 | --color-link: #7f746d; 42 | --color-link-hover: #000; 43 | --color-tagline: #fff; 44 | overflow: hidden; 45 | } 46 | 47 | /* Page Loader */ 48 | .js .loading::before { 49 | content: ''; 50 | position: fixed; 51 | z-index: 100000; 52 | top: 0; 53 | left: 0; 54 | width: 100%; 55 | height: 100%; 56 | background: var(--color-bg); 57 | } 58 | 59 | .js .loading::after { 60 | content: ''; 61 | position: fixed; 62 | z-index: 100000; 63 | top: 50%; 64 | left: 50%; 65 | width: 60px; 66 | height: 60px; 67 | margin: -30px 0 0 -30px; 68 | pointer-events: none; 69 | border-radius: 50%; 70 | opacity: 0.4; 71 | background: var(--color-link); 72 | animation: loaderAnim 0.7s linear infinite alternate forwards; 73 | } 74 | 75 | @keyframes loaderAnim { 76 | to { 77 | opacity: 1; 78 | transform: scale3d(0.5,0.5,1); 79 | } 80 | } 81 | 82 | a { 83 | text-decoration: none; 84 | color: var(--color-link); 85 | outline: none; 86 | } 87 | 88 | a:hover, 89 | a:focus { 90 | color: var(--color-link-hover); 91 | outline: none; 92 | } 93 | 94 | .message { 95 | background: var(--color-text); 96 | color: var(--color-bg); 97 | padding: 1rem; 98 | text-align: center; 99 | } 100 | 101 | .frame { 102 | padding: 3rem 5vw; 103 | text-align: center; 104 | position: relative; 105 | z-index: 1000; 106 | } 107 | 108 | .frame__title { 109 | font-size: 1rem; 110 | margin: 0 0 1rem; 111 | font-weight: 500; 112 | } 113 | 114 | .frame__tagline { 115 | color: var(--color-tagline); 116 | } 117 | 118 | .frame__links { 119 | display: inline; 120 | } 121 | 122 | .frame a { 123 | text-transform: lowercase; 124 | } 125 | 126 | .frame__github, 127 | .frame__links a:not(:last-child), 128 | .frame__demos a:not(:last-child) { 129 | margin-right: 1rem; 130 | } 131 | 132 | .frame__demos { 133 | margin: 1rem 0; 134 | } 135 | 136 | .frame__demo--current, 137 | .frame__demo--current:hover { 138 | color: var(--color-text); 139 | } 140 | 141 | .content { 142 | display: flex; 143 | flex-direction: column; 144 | width: 100vw; 145 | height: calc(100vh - 13rem); 146 | position: relative; 147 | justify-content: flex-start; 148 | align-items: center; 149 | } 150 | 151 | .content__img-wrap { 152 | position: relative; 153 | width: 80%; 154 | height: calc(100% - 13rem); 155 | } 156 | 157 | .content__img { 158 | position: absolute; 159 | top: 0; 160 | left: 0; 161 | width: 100%; 162 | height: 100%; 163 | background: url(../img/large.jpg) no-repeat 50% 100%; 164 | background-size: cover; 165 | opacity: 0.7; 166 | will-change: transform; 167 | } 168 | 169 | .content__img:nth-child(1) { 170 | opacity: 0.3; 171 | } 172 | 173 | .content__img:nth-child(2) { 174 | opacity: 0.5; 175 | } 176 | 177 | .content__text { 178 | position: absolute; 179 | font-size: 10vw; 180 | } 181 | 182 | .js .content__text-inner { 183 | opacity: 0; 184 | } 185 | 186 | .grid { 187 | display: grid; 188 | padding: 1rem; 189 | margin: 0 auto; 190 | grid-template-columns: 100%; 191 | grid-row-gap: 4rem; 192 | grid-column-gap: 20vw; 193 | max-width: 1200px; 194 | } 195 | 196 | .grid__item { 197 | height: 60vh; 198 | max-height: 500px; 199 | position: relative; 200 | } 201 | 202 | .grid__item-img { 203 | position: relative; 204 | width: 100%; 205 | height: 100%; 206 | background-size: cover; 207 | } 208 | 209 | .grid__item-img::after { 210 | content: ''; 211 | background: var(--color-overlay); 212 | position: absolute; 213 | top: 0; 214 | left: 0; 215 | width: 100%; 216 | height: 100%; 217 | mix-blend-mode: color-dodge; 218 | opacity: 0; 219 | transition: opacity 0.3s; 220 | } 221 | 222 | .grid__item:hover .grid__item-img::after { 223 | opacity: 1; 224 | } 225 | 226 | .grid__item-letter { 227 | position: absolute; 228 | bottom: -4vw; 229 | right: -4vw; 230 | z-index: 10; 231 | font-size: 15vw; 232 | line-height: 1; 233 | } 234 | 235 | @media screen and (min-width: 53em) { 236 | .message { 237 | display: none; 238 | } 239 | .frame { 240 | position: fixed; 241 | text-align: left; 242 | z-index: 10000; 243 | top: 0; 244 | left: 0; 245 | display: grid; 246 | align-content: space-between; 247 | width: 100%; 248 | max-width: none; 249 | height: 100vh; 250 | padding: 3rem; 251 | pointer-events: none; 252 | grid-template-columns: 75% 25%; 253 | grid-template-rows: auto auto auto; 254 | grid-template-areas: 'title links' 255 | '... ...' 256 | 'github demos'; 257 | } 258 | .frame__title-wrap { 259 | grid-area: title; 260 | display: flex; 261 | } 262 | .frame__title { 263 | margin: 0; 264 | } 265 | .frame__tagline { 266 | position: relative; 267 | margin: 0 0 0 1rem; 268 | padding: 0 0 0 1rem; 269 | } 270 | .frame__github { 271 | grid-area: github; 272 | justify-self: start; 273 | margin: 0; 274 | } 275 | .frame__demos { 276 | margin: 0; 277 | grid-area: demos; 278 | justify-self: end; 279 | } 280 | .frame__links { 281 | grid-area: links; 282 | padding: 0; 283 | justify-self: end; 284 | } 285 | .frame a { 286 | pointer-events: auto; 287 | } 288 | .content { 289 | height: 100vh; 290 | justify-content: center; 291 | } 292 | .grid { 293 | padding: 70vh 4rem; 294 | grid-template-columns: repeat(2,calc(50% - 10vw)); 295 | grid-row-gap: 50vh; 296 | } 297 | .grid__item:nth-child(even) { 298 | margin-top: -45vh; 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/favicon.ico -------------------------------------------------------------------------------- /img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/1.jpg -------------------------------------------------------------------------------- /img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/10.jpg -------------------------------------------------------------------------------- /img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/2.jpg -------------------------------------------------------------------------------- /img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/3.jpg -------------------------------------------------------------------------------- /img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/4.jpg -------------------------------------------------------------------------------- /img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/5.jpg -------------------------------------------------------------------------------- /img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/6.jpg -------------------------------------------------------------------------------- /img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/7.jpg -------------------------------------------------------------------------------- /img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/8.jpg -------------------------------------------------------------------------------- /img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/9.jpg -------------------------------------------------------------------------------- /img/large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TextDistortionEffects/f4288dd49027f13732e4de0fcfeff748328c295b/img/large.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Text Distortion Effects using Blotter.js | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Text Distortion Effects using Blotter.js

21 |

The scroll speed determines the distortion

22 |
23 | GitHub 24 | 28 |
29 | demo 1 30 | demo 2 31 |
32 |
33 |
34 |
35 |
36 |
A9
37 |
38 |
39 |
40 |
R4
41 |
42 |
43 |
44 |
B7
45 |
46 |
47 |
48 |
K3
49 |
50 |
51 |
52 |
T2
53 |
54 |
55 |
56 |
H8
57 |
58 |
59 |
60 |
X5
61 |
62 |
63 |
64 |
A6
65 |
66 |
67 |
68 |
S2
69 |
70 |
71 |
72 |
W4
73 |
74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Text Distortion Effects with Blotter.js | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
Please view on desktop to see the effect
19 |
20 |
21 |

Text Distortion Effects with Blotter.js

22 |

The mouse speed determines the distortion

23 |
24 | GitHub 25 | 29 |
30 | demo 1 31 | demo 2 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
.01
41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /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 | const MathUtils = { 13 | lineEq: (y2, y1, x2, x1, currentVal) => { 14 | // y = mx + b 15 | var m = (y2 - y1) / (x2 - x1), b = y1 - m * x1; 16 | return m * currentVal + b; 17 | }, 18 | lerp: (a, b, n) => (1 - n) * a + n * b 19 | }; 20 | 21 | class Renderer { 22 | constructor(options, material) { 23 | this.options = options; 24 | this.material = material; 25 | for (let i = 0, len = this.options.uniforms.length; i <= len-1; ++i) { 26 | this.material.uniforms[this.options.uniforms[i].uniform].value = this.options.uniforms[i].value; 27 | } 28 | for (let i = 0, len = this.options.animatable.length; i <= len-1; ++i) { 29 | this[this.options.animatable[i].prop] = this.options.animatable[i].from; 30 | this.material.uniforms[this.options.animatable[i].prop].value = this[this.options.animatable[i].prop]; 31 | } 32 | this.currentScroll = window.pageYOffset; 33 | this.maxScrollSpeed = 80; 34 | requestAnimationFrame(() => this.render()); 35 | } 36 | render() { 37 | const newScroll = window.pageYOffset; 38 | const scrolled = Math.abs(newScroll - this.currentScroll); 39 | for (let i = 0, len = this.options.animatable.length; i <= len-1; ++i) { 40 | this[this.options.animatable[i].prop] = MathUtils.lerp(this[this.options.animatable[i].prop], Math.min(MathUtils.lineEq(this.options.animatable[i].to, this.options.animatable[i].from, this.maxScrollSpeed, 0, scrolled), this.options.animatable[i].to), this.options.easeFactor); 41 | this.material.uniforms[this.options.animatable[i].prop].value = this[this.options.animatable[i].prop]; 42 | } 43 | this.currentScroll = newScroll; 44 | requestAnimationFrame(() => this.render()); 45 | } 46 | } 47 | 48 | class LiquidDistortMaterial { 49 | constructor(options) { 50 | this.options = { 51 | uniforms: [ 52 | { 53 | uniform: 'uSpeed', 54 | value: 0.5 55 | }, 56 | { 57 | uniform: 'uVolatility', 58 | value: 0 59 | }, 60 | { 61 | uniform: 'uSeed', 62 | value: 0.4 63 | } 64 | ], 65 | animatable: [ 66 | {prop: 'uVolatility', from: 0, to: 0.9} 67 | ], 68 | easeFactor: 0.05 69 | }; 70 | Object.assign(this.options, options); 71 | this.material = new Blotter.LiquidDistortMaterial(); 72 | new Renderer(this.options, this.material); 73 | return this.material; 74 | } 75 | } 76 | 77 | class RollingDistortMaterial { 78 | constructor(options) { 79 | this.options = { 80 | uniforms: [ 81 | { 82 | uniform: 'uSineDistortSpread', 83 | value: 0.354 84 | }, 85 | { 86 | uniform: 'uSineDistortCycleCount', 87 | value: 5 88 | }, 89 | { 90 | uniform: 'uSineDistortAmplitude', 91 | value: 0 92 | }, 93 | { 94 | uniform: 'uNoiseDistortVolatility', 95 | value: 0 96 | }, 97 | { 98 | uniform: 'uNoiseDistortAmplitude', 99 | value: 0.168 100 | }, 101 | { 102 | uniform: 'uDistortPosition', 103 | value: [0.38,0.68] 104 | }, 105 | { 106 | uniform: 'uRotation', 107 | value: 48 108 | }, 109 | { 110 | uniform: 'uSpeed', 111 | value: 0.421 112 | } 113 | ], 114 | animatable: [ 115 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.5} 116 | ], 117 | easeFactor: 0.05 118 | }; 119 | Object.assign(this.options, options); 120 | this.material = new Blotter.RollingDistortMaterial(); 121 | new Renderer(this.options, this.material); 122 | return this.material; 123 | } 124 | } 125 | 126 | class ChannelSplitMaterial { 127 | constructor(options) { 128 | this.options = { 129 | uniforms: [ 130 | { 131 | uniform: 'uOffset', 132 | value: 0 133 | }, 134 | { 135 | uniform: 'uRotation', 136 | value: 90 137 | }, 138 | { 139 | uniform: 'uApplyBlur', 140 | value: 1.0 141 | }, 142 | { 143 | uniform: 'uAnimateNoise', 144 | value: 1.0 145 | } 146 | ], 147 | animatable: [ 148 | {prop: 'uOffset', from: 0.02, to: 0.8}, 149 | {prop: 'uRotation', from: 90, to: 100} 150 | ], 151 | easeFactor: 0.05 152 | }; 153 | Object.assign(this.options, options); 154 | this.material = new Blotter.ChannelSplitMaterial(); 155 | new Renderer(this.options, this.material); 156 | return this.material; 157 | } 158 | } 159 | 160 | class Material { 161 | constructor(type, options = {}) { 162 | let material; 163 | switch (type) { 164 | case 'LiquidDistortMaterial': 165 | material = new LiquidDistortMaterial(options); 166 | break; 167 | case 'RollingDistortMaterial': 168 | material = new RollingDistortMaterial(options); 169 | break; 170 | case 'ChannelSplitMaterial': 171 | material = new ChannelSplitMaterial(options); 172 | break; 173 | } 174 | return material; 175 | } 176 | } 177 | 178 | class BlotterEl { 179 | constructor(el, options) { 180 | this.DOM = {el: el}; 181 | this.DOM.textEl = this.DOM.el.querySelector('span.content__text-inner'); 182 | this.style = { 183 | family : "'Goblin One',serif", 184 | size : 130, 185 | paddingLeft: 40, 186 | paddingRight: 40, 187 | paddingTop: 40, 188 | paddingBottom: 40, 189 | fill : "#c69f64" 190 | }; 191 | Object.assign(this.style, options.style); 192 | 193 | this.material = new Material(options.type, options); 194 | this.text = new Blotter.Text(this.DOM.textEl.innerHTML, this.style); 195 | this.blotter = new Blotter(this.material, {texts: this.text}); 196 | this.scope = this.blotter.forText(this.text); 197 | this.DOM.el.removeChild(this.DOM.textEl); 198 | this.scope.appendTo(this.DOM.el); 199 | 200 | const observer = new IntersectionObserver(entries => entries.forEach(entry => this.scope[entry.isIntersecting ? 'play' : 'pause']())); 201 | observer.observe(this.scope.domElement); 202 | } 203 | } 204 | 205 | const config = [ 206 | { 207 | type: 'LiquidDistortMaterial', 208 | uniforms: [{uniform: 'uSpeed', value: 0.6},{uniform: 'uVolatility', value: 0},{uniform: 'uSeed', value: 0.4}], 209 | animatable: [ 210 | {prop: 'uVolatility', from: 0, to: 0.4} 211 | ], 212 | easeFactor: 0.05 213 | }, 214 | { 215 | type: 'LiquidDistortMaterial', 216 | uniforms: [{uniform: 'uSpeed', value: 0.9},{uniform: 'uVolatility', value: 0},{uniform: 'uSeed', value: 0.1}], 217 | animatable: [ 218 | {prop: 'uVolatility', from: 0, to: 2} 219 | ], 220 | easeFactor: 0.1 221 | }, 222 | { 223 | type: 'RollingDistortMaterial', 224 | uniforms: [ 225 | { 226 | uniform: 'uSineDistortSpread', 227 | value: 0.354 228 | }, 229 | { 230 | uniform: 'uSineDistortCycleCount', 231 | value: 5 232 | }, 233 | { 234 | uniform: 'uSineDistortAmplitude', 235 | value: 0 236 | }, 237 | { 238 | uniform: 'uNoiseDistortVolatility', 239 | value: 0 240 | }, 241 | { 242 | uniform: 'uNoiseDistortAmplitude', 243 | value: 0.168 244 | }, 245 | { 246 | uniform: 'uDistortPosition', 247 | value: [0.38,0.68] 248 | }, 249 | { 250 | uniform: 'uRotation', 251 | value: 48 252 | }, 253 | { 254 | uniform: 'uSpeed', 255 | value: 0.421 256 | } 257 | ], 258 | animatable: [ 259 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.5} 260 | ], 261 | easeFactor: 0.15 262 | }, 263 | { 264 | type: 'RollingDistortMaterial', 265 | uniforms: [ 266 | { 267 | uniform: 'uSineDistortSpread', 268 | value: 0.54 269 | }, 270 | { 271 | uniform: 'uSineDistortCycleCount', 272 | value: 2 273 | }, 274 | { 275 | uniform: 'uSineDistortAmplitude', 276 | value: 0 277 | }, 278 | { 279 | uniform: 'uNoiseDistortVolatility', 280 | value: 0 281 | }, 282 | { 283 | uniform: 'uNoiseDistortAmplitude', 284 | value: 0.15 285 | }, 286 | { 287 | uniform: 'uDistortPosition', 288 | value: [0.18,0.98] 289 | }, 290 | { 291 | uniform: 'uRotation', 292 | value: 90 293 | }, 294 | { 295 | uniform: 'uSpeed', 296 | value: 0.3 297 | } 298 | ], 299 | animatable: [ 300 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.2} 301 | ], 302 | easeFactor: 0.05 303 | }, 304 | { 305 | type: 'RollingDistortMaterial', 306 | uniforms: [ 307 | { 308 | uniform: 'uSineDistortSpread', 309 | value: 0.44 310 | }, 311 | { 312 | uniform: 'uSineDistortCycleCount', 313 | value: 5 314 | }, 315 | { 316 | uniform: 'uSineDistortAmplitude', 317 | value: 0 318 | }, 319 | { 320 | uniform: 'uNoiseDistortVolatility', 321 | value: 0 322 | }, 323 | { 324 | uniform: 'uNoiseDistortAmplitude', 325 | value: 0.85 326 | }, 327 | { 328 | uniform: 'uDistortPosition', 329 | value: [0,0] 330 | }, 331 | { 332 | uniform: 'uRotation', 333 | value: 0 334 | }, 335 | { 336 | uniform: 'uSpeed', 337 | value: .1 338 | } 339 | ], 340 | animatable: [ 341 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.2} 342 | ], 343 | easeFactor: 0.35 344 | }, 345 | { 346 | type: 'RollingDistortMaterial', 347 | uniforms: [ 348 | { 349 | uniform: 'uSineDistortSpread', 350 | value: 0.74 351 | }, 352 | { 353 | uniform: 'uSineDistortCycleCount', 354 | value: 7 355 | }, 356 | { 357 | uniform: 'uSineDistortAmplitude', 358 | value: 0 359 | }, 360 | { 361 | uniform: 'uNoiseDistortVolatility', 362 | value: 0 363 | }, 364 | { 365 | uniform: 'uNoiseDistortAmplitude', 366 | value: 0.15 367 | }, 368 | { 369 | uniform: 'uDistortPosition', 370 | value: [0.1,0.5] 371 | }, 372 | { 373 | uniform: 'uRotation', 374 | value: 20 375 | }, 376 | { 377 | uniform: 'uSpeed', 378 | value: 0.7 379 | } 380 | ], 381 | animatable: [ 382 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.2} 383 | ], 384 | easeFactor: 0.1 385 | }, 386 | { 387 | type: 'RollingDistortMaterial', 388 | uniforms: [ 389 | { 390 | uniform: 'uSineDistortSpread', 391 | value: 0.084 392 | }, 393 | { 394 | uniform: 'uSineDistortCycleCount', 395 | value: 2.2 396 | }, 397 | { 398 | uniform: 'uSineDistortAmplitude', 399 | value: 0 400 | }, 401 | { 402 | uniform: 'uNoiseDistortVolatility', 403 | value: 0 404 | }, 405 | { 406 | uniform: 'uNoiseDistortAmplitude', 407 | value: 0 408 | }, 409 | { 410 | uniform: 'uDistortPosition', 411 | value: [0.35,0.37] 412 | }, 413 | { 414 | uniform: 'uRotation', 415 | value: 180 416 | }, 417 | { 418 | uniform: 'uSpeed', 419 | value: 0.94 420 | } 421 | ], 422 | animatable: [ 423 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.13} 424 | ], 425 | easeFactor: 0.15 426 | }, 427 | { 428 | type: 'RollingDistortMaterial', 429 | uniforms: [ 430 | { 431 | uniform: 'uSineDistortSpread', 432 | value: 0 433 | }, 434 | { 435 | uniform: 'uSineDistortCycleCount', 436 | value: 0 437 | }, 438 | { 439 | uniform: 'uSineDistortAmplitude', 440 | value: 0 441 | }, 442 | { 443 | uniform: 'uNoiseDistortVolatility', 444 | value: 0.01 445 | }, 446 | { 447 | uniform: 'uNoiseDistortAmplitude', 448 | value: 0.126 449 | }, 450 | { 451 | uniform: 'uDistortPosition', 452 | value: [0.3,0.3] 453 | }, 454 | { 455 | uniform: 'uRotation', 456 | value: 180 457 | }, 458 | { 459 | uniform: 'uSpeed', 460 | value: 0.13 461 | } 462 | ], 463 | animatable: [ 464 | {prop: 'uNoiseDistortVolatility', from: 0.01, to: 200}, 465 | {prop: 'uRotation', from: 180, to: 270} 466 | ], 467 | easeFactor: 0.25 468 | }, 469 | { 470 | type: 'RollingDistortMaterial', 471 | uniforms: [ 472 | { 473 | uniform: 'uSineDistortSpread', 474 | value: 0.1 475 | }, 476 | { 477 | uniform: 'uSineDistortCycleCount', 478 | value: 0 479 | }, 480 | { 481 | uniform: 'uSineDistortAmplitude', 482 | value: 0 483 | }, 484 | { 485 | uniform: 'uNoiseDistortVolatility', 486 | value: 0 487 | }, 488 | { 489 | uniform: 'uNoiseDistortAmplitude', 490 | value: 0 491 | }, 492 | { 493 | uniform: 'uDistortPosition', 494 | value: [0,0] 495 | }, 496 | { 497 | uniform: 'uRotation', 498 | value: 90 499 | }, 500 | { 501 | uniform: 'uSpeed', 502 | value: 2 503 | } 504 | ], 505 | animatable: [ 506 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.3}, 507 | {prop: 'uSineDistortCycleCount', from: 0, to: 1.5}, 508 | ], 509 | easeFactor: 0.35 510 | }, 511 | { 512 | type: 'RollingDistortMaterial', 513 | uniforms: [ 514 | { 515 | uniform: 'uSineDistortSpread', 516 | value: 0.28 517 | }, 518 | { 519 | uniform: 'uSineDistortCycleCount', 520 | value: 7 521 | }, 522 | { 523 | uniform: 'uSineDistortAmplitude', 524 | value: 0 525 | }, 526 | { 527 | uniform: 'uNoiseDistortVolatility', 528 | value: 0 529 | }, 530 | { 531 | uniform: 'uNoiseDistortAmplitude', 532 | value: 0 533 | }, 534 | { 535 | uniform: 'uDistortPosition', 536 | value: [0,0] 537 | }, 538 | { 539 | uniform: 'uRotation', 540 | value: 90 541 | }, 542 | { 543 | uniform: 'uSpeed', 544 | value: 0.3 545 | } 546 | ], 547 | animatable: [ 548 | {prop: 'uSineDistortAmplitude', from: 0, to: 0.2} 549 | ], 550 | easeFactor: 0.65 551 | } 552 | ]; 553 | 554 | // Preload fonts. 555 | WebFont.load({ 556 | google: {families: ['Goblin+One']}, 557 | active: () => [...document.querySelectorAll('[data-blotter]')].forEach((el, pos) => new BlotterEl(el, config[pos])) 558 | }); 559 | 560 | // Preload all the images in the page. 561 | imagesLoaded(document.querySelectorAll('.grid__item-img'), {background: true}, () => document.body.classList.remove('loading')); 562 | } -------------------------------------------------------------------------------- /js/demo2.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 | const body = document.body; 13 | const docEl = document.documentElement; 14 | 15 | const MathUtils = { 16 | lineEq: (y2, y1, x2, x1, currentVal) => { 17 | // y = mx + b 18 | var m = (y2 - y1) / (x2 - x1), b = y1 - m * x1; 19 | return m * currentVal + b; 20 | }, 21 | lerp: (a, b, n) => (1 - n) * a + n * b, 22 | distance: (x1, x2, y1, y2) => { 23 | var a = x1 - x2; 24 | var b = y1 - y2; 25 | return Math.hypot(a,b); 26 | } 27 | }; 28 | 29 | let winsize; 30 | const calcWinsize = () => winsize = {width: window.innerWidth, height: window.innerHeight}; 31 | calcWinsize(); 32 | window.addEventListener('resize', calcWinsize); 33 | 34 | const getMousePos = (ev) => { 35 | let posx = 0; 36 | let posy = 0; 37 | if (!ev) ev = window.event; 38 | if (ev.pageX || ev.pageY) { 39 | posx = ev.pageX; 40 | posy = ev.pageY; 41 | } 42 | else if (ev.clientX || ev.clientY) { 43 | posx = ev.clientX + body.scrollLeft + docEl.scrollLeft; 44 | posy = ev.clientY + body.scrollTop + docEl.scrollTop; 45 | } 46 | return {x: posx, y: posy}; 47 | } 48 | 49 | let mousePos = {x: winsize.width/2, y: winsize.height/2}; 50 | window.addEventListener('mousemove', ev => mousePos = getMousePos(ev)); 51 | 52 | const imgs = [...document.querySelectorAll('.content__img')]; 53 | const imgsTotal = imgs.length; 54 | let imgTranslations = [...new Array(imgsTotal)].map(() => ({x: 0, y: 0})); 55 | 56 | const elem = document.querySelector('.content__text'); 57 | const textEl = elem.querySelector('span.content__text-inner'); 58 | 59 | const createBlotterText = () => { 60 | const text = new Blotter.Text(textEl.innerHTML, { 61 | family : "'Playfair Display',serif", 62 | weight: 900, 63 | size : 200, 64 | paddingLeft: 100, 65 | paddingRight: 100, 66 | paddingTop: 100, 67 | paddingBottom: 100, 68 | fill : 'white' 69 | }); 70 | elem.removeChild(textEl); 71 | 72 | const material = new Blotter.LiquidDistortMaterial(); 73 | material.uniforms.uSpeed.value = 1; 74 | material.uniforms.uVolatility.value = 0; 75 | material.uniforms.uSeed.value = 0.1; 76 | const blotter = new Blotter(material, {texts: text}); 77 | const scope = blotter.forText(text); 78 | scope.appendTo(elem); 79 | 80 | let lastMousePosition = {x: winsize.width/2, y: winsize.height/2}; 81 | let volatility = 0; 82 | 83 | const render = () => { 84 | const docScrolls = {left : body.scrollLeft + docEl.scrollLeft, top : body.scrollTop + docEl.scrollTop}; 85 | const relmousepos = {x : mousePos.x - docScrolls.left, y : mousePos.y - docScrolls.top }; 86 | const mouseDistance = MathUtils.distance(lastMousePosition.x, relmousepos.x, lastMousePosition.y, relmousepos.y); 87 | 88 | volatility = MathUtils.lerp(volatility, Math.min(MathUtils.lineEq(0.9, 0, 100, 0, mouseDistance),0.9), 0.05); 89 | material.uniforms.uVolatility.value = volatility; 90 | 91 | for (let i = 0; i <= imgsTotal - 1; ++i) { 92 | imgTranslations[i].x = MathUtils.lerp(imgTranslations[i].x, MathUtils.lineEq(40, -40, winsize.width, 0, relmousepos.x), i === imgsTotal - 1 ? 0.15 : 0.03*i + 0.03); 93 | imgTranslations[i].y = MathUtils.lerp(imgTranslations[i].y, MathUtils.lineEq(40, -40, winsize.height, 0, relmousepos.y), i === imgsTotal - 1 ? 0.15 : 0.03*i + 0.03); 94 | imgs[i].style.transform = `translateX(${(imgTranslations[i].x)}px) translateY(${imgTranslations[i].y}px)`; 95 | }; 96 | 97 | lastMousePosition = {x: relmousepos.x, y: relmousepos.y}; 98 | requestAnimationFrame(render); 99 | } 100 | requestAnimationFrame(render); 101 | }; 102 | 103 | WebFont.load({ 104 | google: { 105 | families: ['Playfair+Display:900'] 106 | }, 107 | active: () => createBlotterText() 108 | }); 109 | 110 | // Preload all the images in the page. 111 | imagesLoaded(document.querySelectorAll('.content__img'), {background: true}, () => body.classList.remove('loading')); 112 | } -------------------------------------------------------------------------------- /js/imagesloaded.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v4.1.4 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | !function(e,t){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",t):"object"==typeof module&&module.exports?module.exports=t():e.EvEmitter=t()}("undefined"!=typeof window?window:this,function(){function e(){}var t=e.prototype;return t.on=function(e,t){if(e&&t){var i=this._events=this._events||{},n=i[e]=i[e]||[];return n.indexOf(t)==-1&&n.push(t),this}},t.once=function(e,t){if(e&&t){this.on(e,t);var i=this._onceEvents=this._onceEvents||{},n=i[e]=i[e]||{};return n[t]=!0,this}},t.off=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){var n=i.indexOf(t);return n!=-1&&i.splice(n,1),this}},t.emitEvent=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){i=i.slice(0),t=t||[];for(var n=this._onceEvents&&this._onceEvents[e],o=0;o= 270.0) {", 44 | " k *= -1.0;", 45 | " }", 46 | 47 | 48 | " return k;", 49 | "}", 50 | 51 | 52 | "float noiseWithWidthAtUv(float width, vec2 uv) {", 53 | " float noiseModifier = 1.0;", 54 | " if (uAnimateNoise > 0.0) {", 55 | " noiseModifier = sin(uGlobalTime);", 56 | " }", 57 | 58 | " vec2 noiseRowCol = floor((uv * uResolution.xy) / width);", 59 | " vec2 noiseFragCoord = ((noiseRowCol * width) + (width / 2.0));", 60 | " vec2 noiseUv = noiseFragCoord / uResolution.xy;", 61 | 62 | " return random(noiseUv * noiseModifier) * 0.125;", 63 | "}", 64 | 65 | 66 | "vec4 motionBlur(vec2 uv, vec2 blurOffset, float maxOffset) {", 67 | " float noiseWidth = 3.0;", 68 | " float randNoise = noiseWithWidthAtUv(noiseWidth, uv);", 69 | 70 | " vec4 result = textTexture(uv);", 71 | 72 | " float maxStepsReached = 0.0;", 73 | 74 | " // Note: Step by 2 to optimize performance. We conceal lossiness here via applied noise.", 75 | " // If you do want maximum fidelity, change `i += 2` to `i += 1` below.", 76 | " for (int i = 1; i <= MAX_STEPS; i += 2) {", 77 | " if (abs(float(i)) > maxOffset) { break; }", 78 | " maxStepsReached += 1.0;", 79 | 80 | " // Divide blurOffset by 2.0 so that motion blur starts half way behind itself", 81 | " // preventing blur from shoving samples in any direction", 82 | " vec2 offset = (blurOffset / 2.0) - (blurOffset * (float(i) / maxOffset));", 83 | " vec4 stepSample = textTexture(uv + (offset / uResolution.xy));",, 84 | 85 | " result += stepSample;", 86 | " }", 87 | 88 | " if (maxOffset >= 1.0) {", 89 | " result /= maxStepsReached;", 90 | " //result.a = pow(result.a, 2.0); // Apply logarithmic smoothing to alpha", 91 | " result.a -= (randNoise * (1.0 - result.a)); // Apply noise to smoothed alpha", 92 | " }", 93 | 94 | 95 | " return result;", 96 | "}", 97 | 98 | 99 | "void mainImage( out vec4 mainImage, in vec2 fragCoord ) {", 100 | 101 | " // Setup", 102 | " // -------------------------------", 103 | 104 | " vec2 uv = fragCoord.xy / uResolution.xy;", 105 | 106 | " float offset = min(float(MAX_STEPS), uResolution.y * uOffset);", 107 | 108 | " float slope = normalizedSlope(slopeForDegrees(uRotation), uResolution);", 109 | 110 | " // We want the blur to be the full offset amount in each direction", 111 | " // and to adjust with our logarithmic adjustment made later, so multiply by 4", 112 | " float adjustedOffset = offset;// * 4.0;", 113 | 114 | " vec2 blurOffset = motionBlurOffsets(fragCoord, uRotation, adjustedOffset);", 115 | 116 | 117 | " // Set Starting Points", 118 | " // -------------------------------", 119 | 120 | " vec2 rUv = uv;", 121 | " vec2 gUv = uv;", 122 | " vec2 bUv = uv;", 123 | 124 | " vec2 k = offsetsForCoordAtDistanceOnSlope(offset, slope) / uResolution;", 125 | 126 | " if (uRotation <= 90.0 || uRotation >= 270.0) {", 127 | " rUv += k;", 128 | " bUv -= k;", 129 | " }", 130 | " else {", 131 | " rUv -= k;", 132 | " bUv += k;", 133 | " }", 134 | 135 | 136 | " // Blur and Split Channels", 137 | " // -------------------------------", 138 | 139 | " vec4 resultR = vec4(0.0);", 140 | " vec4 resultG = vec4(0.0);", 141 | " vec4 resultB = vec4(0.0);", 142 | 143 | " if (uApplyBlur > 0.0) {", 144 | " resultR = motionBlur(rUv, blurOffset, adjustedOffset);", 145 | " resultG = motionBlur(gUv, blurOffset, adjustedOffset);", 146 | " resultB = motionBlur(bUv, blurOffset, adjustedOffset);", 147 | " } else {", 148 | " resultR = textTexture(rUv);", 149 | " resultG = textTexture(gUv);", 150 | " resultB = textTexture(bUv);", 151 | " }", 152 | 153 | " float a = resultR.a + resultG.a + resultB.a;", 154 | 155 | " resultR = normalBlend(resultR, uBlendColor);", 156 | " resultG = normalBlend(resultG, uBlendColor);", 157 | " resultB = normalBlend(resultB, uBlendColor);", 158 | 159 | 160 | 161 | " mainImage = vec4(resultR.r, resultG.g, resultB.b, a);", 162 | "}" 163 | ].join("\n"); 164 | 165 | return mainImageSrc; 166 | } 167 | 168 | return { 169 | 170 | constructor : Blotter.ChannelSplitMaterial, 171 | 172 | init : function () { 173 | this.mainImage = _mainImageSrc(); 174 | this.uniforms = { 175 | uOffset : { type : "1f", value : 0.05 }, 176 | uRotation : { type : "1f", value : 45.0 }, 177 | uApplyBlur : { type : "1f", value : 1.0 }, 178 | uAnimateNoise : { type : "1f", value : 1.0 } 179 | }; 180 | } 181 | }; 182 | 183 | })()); 184 | 185 | })( 186 | this.Blotter 187 | ); 188 | -------------------------------------------------------------------------------- /js/materials/fliesMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.FliesMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.FliesMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.FliesMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.Random, 14 | 15 | "vec2 random2(vec2 p) {", 16 | " return fract(sin(vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)))) * 43758.5453);", 17 | "}", 18 | 19 | "float isParticle(out vec3 particleColor, vec2 fragCoord, float pointRadius, float pointCellWidth, float dodge, vec2 dodgePosition, float dodgeSpread, float speed) { ", 20 | " if (pointCellWidth == 0.0) { return 0.0; };", 21 | 22 | " vec2 uv = fragCoord.xy / uResolution.xy;", 23 | 24 | " float pointRadiusOfCell = pointRadius / pointCellWidth;", 25 | 26 | " vec2 totalCellCount = uResolution.xy / pointCellWidth;", 27 | " vec2 cellUv = uv * totalCellCount;", 28 | 29 | " // Tile the space", 30 | " vec2 iUv = floor(cellUv);", 31 | " vec2 fUv = fract(cellUv);", 32 | 33 | " float minDist = 1.0; // minimun distance", 34 | 35 | " vec4 baseSample = textTexture(cellUv);", 36 | " float maxWeight = 0.0;", 37 | " particleColor = baseSample.rgb;", 38 | 39 | " for (int y= -1; y <= 1; y++) {", 40 | " for (int x= -1; x <= 1; x++) {", 41 | " // Neighbor place in the grid", 42 | " vec2 neighbor = vec2(float(x), float(y));", 43 | 44 | " // Random position from current + neighbor place in the grid", 45 | " vec2 point = random2(iUv + neighbor);", 46 | 47 | " // Get cell weighting from cell's center alpha", 48 | " vec2 cellRowCol = floor(fragCoord / pointCellWidth) + neighbor;", 49 | " vec2 cellFragCoord = ((cellRowCol * pointCellWidth) + (pointCellWidth / 2.0));", 50 | " vec4 cellSample = textTexture(cellFragCoord / uResolution.xy);", 51 | " float cellWeight = cellSample.a;", 52 | 53 | " if (cellWeight < 1.0) {", 54 | " // If the cell is not fully within our text, we should disregard it", 55 | " continue;", 56 | " }", 57 | 58 | " maxWeight = max(maxWeight, cellWeight);", 59 | " if (cellWeight == maxWeight) {", 60 | " particleColor = cellSample.rgb;", 61 | " }", 62 | 63 | " float distanceFromDodge = distance(dodgePosition * uResolution.xy, cellFragCoord) / uResolution.y;", 64 | " distanceFromDodge = 1.0 - smoothstep(0.0, dodgeSpread, distanceFromDodge);", 65 | 66 | " // Apply weighting to noise and dodge if dodge is set to 1.0", 67 | " cellWeight = step(cellWeight, random(cellRowCol)) + (distanceFromDodge * dodge);", 68 | 69 | " // Animate the point", 70 | " point = 0.5 + 0.75 * sin((uGlobalTime * speed) + 6.2831 * point);", 71 | 72 | " // Vector between the pixel and the point", 73 | " vec2 diff = neighbor + point - fUv;", 74 | 75 | " // Distance to the point", 76 | " float dist = length(diff);", 77 | " dist += cellWeight; // Effectively remove point", 78 | 79 | " // Keep the closer distance", 80 | " minDist = min(minDist, dist);", 81 | " }", 82 | " }", 83 | 84 | 85 | " float pointEasing = pointRadiusOfCell - (1.0 / pointCellWidth);", 86 | 87 | " float isParticle = 1.0 - smoothstep(pointEasing, pointRadiusOfCell, minDist);", 88 | 89 | " return isParticle;", 90 | "}", 91 | 92 | "void mainImage( out vec4 mainImage, in vec2 fragCoord ) {", 93 | " vec2 uv = fragCoord.xy / uResolution.xy;", 94 | 95 | " // Convert uPointCellWidth to pixels, keeping it between 1 and the total y resolution of the text", 96 | " // Note: floor uPointCellWidth here so that we dont have half pixel widths on retina displays", 97 | " float pointCellWidth = floor(max(0.0, min(1.0, uPointCellWidth) * uResolution.y));", 98 | 99 | " // Ensure uPointRadius allow points to exceed the width of their cells", 100 | " float pointRadius = uPointRadius * 0.8;", 101 | " pointRadius = min(pointRadius * pointCellWidth, pointCellWidth);", 102 | 103 | " float dodge = ceil(uDodge);", 104 | 105 | " vec3 outColor = vec3(0.0);", 106 | " float point = isParticle(outColor, fragCoord, pointRadius, pointCellWidth, dodge, uDodgePosition, uDodgeSpread, uSpeed);", 107 | 108 | " mainImage = vec4(outColor, point);", 109 | "}" 110 | ].join("\n"); 111 | 112 | return mainImageSrc; 113 | } 114 | 115 | return { 116 | 117 | constructor : Blotter.FliesMaterial, 118 | 119 | init : function () { 120 | this.mainImage = _mainImageSrc(); 121 | this.uniforms = { 122 | uPointCellWidth : { type : "1f", value : 0.04 }, 123 | uPointRadius : { type : "1f", value : 0.75 }, 124 | uDodge : { type : "1f", value : 0.0 }, 125 | uDodgePosition : { type : "2f", value : [0.5, 0.5] }, 126 | uDodgeSpread : { type : "1f", value : 0.25 }, 127 | uSpeed : { type : "1f", value : 1.0 } 128 | }; 129 | } 130 | }; 131 | 132 | })()); 133 | 134 | })( 135 | this.Blotter 136 | ); 137 | -------------------------------------------------------------------------------- /js/materials/liquidDistortMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.LiquidDistortMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.LiquidDistortMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.LiquidDistortMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.Noise3D, 14 | 15 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 16 | "{", 17 | " // Setup ========================================================================", 18 | 19 | " vec2 uv = fragCoord.xy / uResolution.xy;", 20 | " float z = uSeed + uGlobalTime * uSpeed;", 21 | 22 | " uv += snoise(vec3(uv, z)) * uVolatility;", 23 | 24 | " mainImage = textTexture(uv);", 25 | 26 | "}" 27 | ].join("\n"); 28 | 29 | return mainImageSrc; 30 | } 31 | 32 | return { 33 | 34 | constructor : Blotter.LiquidDistortMaterial, 35 | 36 | init : function () { 37 | this.mainImage = _mainImageSrc(); 38 | this.uniforms = { 39 | uSpeed : { type : "1f", value : 1.0 }, 40 | uVolatility : { type : "1f", value : 0.15 }, 41 | uSeed : { type : "1f", value : 0.1 } 42 | }; 43 | } 44 | }; 45 | 46 | })()); 47 | 48 | })( 49 | this.Blotter 50 | ); 51 | -------------------------------------------------------------------------------- /js/materials/rollingDistortMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.RollingDistortMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.RollingDistortMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.RollingDistortMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.PI, 14 | Blotter.Assets.Shaders.LineMath, 15 | Blotter.Assets.Shaders.Noise, 16 | 17 | "// Fix a floating point number to two decimal places", 18 | "float toFixedTwo(float f) {", 19 | " return float(int(f * 100.0)) / 100.0;", 20 | "}", 21 | 22 | "// Via: http://www.iquilezles.org/www/articles/functions/functions.htm", 23 | "float impulse(float k, float x) {", 24 | " float h = k * x;", 25 | " return h * exp(1.0 - h);", 26 | "}", 27 | 28 | "vec2 waveOffset(vec2 fragCoord, float sineDistortSpread, float sineDistortCycleCount, float sineDistortAmplitude, float noiseDistortVolatility, float noiseDistortAmplitude, vec2 distortPosition, float deg, float speed) {", 29 | 30 | " // Setup", 31 | " // -------------------------------", 32 | 33 | " deg = toFixedTwo(deg);", 34 | 35 | " float centerDistance = 0.5;", 36 | " vec2 centerUv = vec2(centerDistance);", 37 | " vec2 centerCoord = uResolution.xy * centerUv;", 38 | 39 | " float changeOverTime = uGlobalTime * speed;", 40 | 41 | " float slope = normalizedSlope(slopeForDegrees(deg), uResolution.xy);", 42 | " float perpendicularDeg = mod(deg + 90.0, 360.0); // Offset angle by 90.0, but keep it from exceeding 360.0", 43 | " float perpendicularSlope = normalizedSlope(slopeForDegrees(perpendicularDeg), uResolution.xy);", 44 | 45 | 46 | " // Find intersects for line with edges of viewport", 47 | " // -------------------------------", 48 | 49 | " vec2 edgeIntersectA = vec2(0.0);", 50 | " vec2 edgeIntersectB = vec2(0.0);", 51 | " intersectsOnRectForLine(edgeIntersectA, edgeIntersectB, vec2(0.0), uResolution.xy, centerCoord, slope);", 52 | " float crossSectionLength = distance(edgeIntersectA, edgeIntersectB);", 53 | 54 | " // Find the threshold for degrees at which our intersectsOnRectForLine function would flip", 55 | " // intersects A and B because of the order in which it finds them. This is the angle at which", 56 | " // the y coordinate for the hypotenuse of a right triangle whose oposite adjacent edge runs from", 57 | " // vec2(0.0, centerCoord.y) to centerCoord and whose opposite edge runs from vec2(0.0, centerCoord.y) to", 58 | " // vec2(0.0, uResolution.y) exceeeds uResolution.y", 59 | " float thresholdDegA = atan(centerCoord.y / centerCoord.x) * (180.0 / PI);", 60 | " float thresholdDegB = mod(thresholdDegA + 180.0, 360.0);", 61 | 62 | " vec2 edgeIntersect = vec2(0.0);", 63 | " if (deg < thresholdDegA || deg > thresholdDegB) {", 64 | " edgeIntersect = edgeIntersectA;", 65 | " } else {", 66 | " edgeIntersect = edgeIntersectB;", 67 | " }", 68 | 69 | " vec2 perpendicularIntersectA = vec2(0.0);", 70 | " vec2 perpendicularIntersectB = vec2(0.0);", 71 | " intersectsOnRectForLine(perpendicularIntersectA, perpendicularIntersectB, vec2(0.0), uResolution.xy, centerCoord, perpendicularSlope); ", 72 | " float perpendicularLength = distance(perpendicularIntersectA, perpendicularIntersectA);", 73 | 74 | " vec2 coordLineIntersect = vec2(0.0);", 75 | " lineLineIntersection(coordLineIntersect, centerCoord, slope, fragCoord, perpendicularSlope);", 76 | 77 | 78 | " // Define placement for distortion ", 79 | " // -------------------------------", 80 | 81 | " vec2 distortPositionIntersect = vec2(0.0);", 82 | " lineLineIntersection(distortPositionIntersect, distortPosition * uResolution.xy, perpendicularSlope, edgeIntersect, slope);", 83 | " float distortDistanceFromEdge = (distance(edgeIntersect, distortPositionIntersect) / crossSectionLength);// + sineDistortSpread;", 84 | 85 | " float uvDistanceFromDistort = distance(edgeIntersect, coordLineIntersect) / crossSectionLength;", 86 | " float noiseDistortVarianceAdjuster = uvDistanceFromDistort + changeOverTime;", 87 | " uvDistanceFromDistort += -centerDistance + distortDistanceFromEdge + changeOverTime;", 88 | " uvDistanceFromDistort = mod(uvDistanceFromDistort, 1.0); // For sine, keep distance between 0.0 and 1.0", 89 | 90 | 91 | " // Define sine distortion ", 92 | " // -------------------------------", 93 | 94 | " float minThreshold = centerDistance - sineDistortSpread;", 95 | " float maxThreshold = centerDistance + sineDistortSpread;", 96 | 97 | " uvDistanceFromDistort = clamp(((uvDistanceFromDistort - minThreshold)/(maxThreshold - minThreshold)), 0.0, 1.0);", 98 | " if (sineDistortSpread < 0.5) {", 99 | " // Add smoother decay to sin distort when it isnt taking up the full viewport.", 100 | " uvDistanceFromDistort = impulse(uvDistanceFromDistort, uvDistanceFromDistort);", 101 | " }", 102 | 103 | " float sineDistortion = sin(uvDistanceFromDistort * PI * sineDistortCycleCount) * sineDistortAmplitude;", 104 | 105 | 106 | " // Define noise distortion ", 107 | " // -------------------------------", 108 | 109 | " float noiseDistortion = noise(noiseDistortVolatility * noiseDistortVarianceAdjuster) * noiseDistortAmplitude;", 110 | " if (noiseDistortVolatility > 0.0) {", 111 | " noiseDistortion -= noiseDistortAmplitude / 2.0; // Adjust primary distort so that it distorts in two directions.", 112 | " }", 113 | " noiseDistortion *= (sineDistortion > 0.0 ? 1.0 : -1.0); // Adjust primary distort to account for sin variance.", 114 | 115 | 116 | " // Combine distortions to find UV offsets ", 117 | " // -------------------------------", 118 | 119 | " vec2 kV = offsetsForCoordAtDistanceOnSlope(sineDistortion + noiseDistortion, perpendicularSlope);", 120 | " if (deg <= 0.0 || deg >= 180.0) {", 121 | " kV *= -1.0;", 122 | " }", 123 | 124 | 125 | " return kV;", 126 | "}", 127 | 128 | 129 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 130 | "{", 131 | " // Setup", 132 | " // -------------------------------", 133 | 134 | " vec2 uv = fragCoord.xy / uResolution.xy;", 135 | 136 | " // Minor hacks to ensure our waves start horizontal and animating in a downward direction by default.", 137 | " uRotation = mod(uRotation + 270.0, 360.0);", 138 | " uDistortPosition.y = 1.0 - uDistortPosition.y;", 139 | 140 | 141 | " // Distortion", 142 | " // -------------------------------", 143 | 144 | " vec2 offset = waveOffset(fragCoord, uSineDistortSpread, uSineDistortCycleCount, uSineDistortAmplitude, uNoiseDistortVolatility, uNoiseDistortAmplitude, uDistortPosition, uRotation, uSpeed);", 145 | 146 | " mainImage = textTexture(uv + offset);", 147 | "}" 148 | ].join("\n"); 149 | 150 | return mainImageSrc; 151 | } 152 | 153 | return { 154 | 155 | constructor : Blotter.RollingDistortMaterial, 156 | 157 | init : function () { 158 | this.mainImage = _mainImageSrc(); 159 | this.uniforms = { 160 | uSineDistortSpread : { type : "1f", value : 0.05 }, 161 | uSineDistortCycleCount : { type : "1f", value : 2.0 }, 162 | uSineDistortAmplitude : { type : "1f", value : 0.25 }, 163 | uNoiseDistortVolatility : { type : "1f", value : 20.0 }, 164 | uNoiseDistortAmplitude : { type : "1f", value : 0.01 }, 165 | uDistortPosition : { type : "2f", value : [0.5, 0.5] }, 166 | uRotation : { type : "1f", value : 170.0 }, 167 | uSpeed : { type : "1f", value : 0.08 } 168 | }; 169 | } 170 | }; 171 | 172 | })()); 173 | 174 | })( 175 | this.Blotter 176 | ); 177 | -------------------------------------------------------------------------------- /js/materials/slidingDoorMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.SlidingDoorMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.SlidingDoorMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.SlidingDoorMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.PI, 14 | 15 | "float easingForPositionWithDeadzoneWidth(float position, float deadzoneWidth) {", 16 | " // Offset position buy 0.25 so that sin wave begins on a downslope at 0.0", 17 | " position += 0.25;", 18 | 19 | " // Distance of adjusted position from 0.75, min of 0.0 and max of 0.5", 20 | " float firstDist = distance(position, 0.75);", 21 | 22 | " // Divide deadzoneWidth by two, as we will be working it out in either direction from the center position.", 23 | " float halfDeadzoneWidth = deadzoneWidth / 2.0;", 24 | 25 | " // Clamp distance of position from center (0.75) to something between 0.5 and the halfDeadzoneWidth from center.", 26 | " float removedDistance = max(firstDist, halfDeadzoneWidth);", 27 | 28 | " // Find the percentage of removedDistance within the range of halfDeadzoneWidth..0.5", 29 | " float distanceOfRange = (removedDistance - halfDeadzoneWidth) / (0.5 - halfDeadzoneWidth);", 30 | 31 | " // Convert distanceOfRange to a number between 0.0 and 0.5. This means that for any pixel +/- halfDeadzoneWidth from center, the value will be 0.5.", 32 | " float offsetDist = (0.5 * (1.0 - (distanceOfRange)));", 33 | 34 | " if (position < 0.75) {", 35 | " position = 0.25 + offsetDist;", 36 | " } else {", 37 | " position = 1.25 - offsetDist;", 38 | " }", 39 | 40 | 41 | " return sin((position) * PI * 2.0) / 2.0;", 42 | "}", 43 | 44 | 45 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 46 | "{", 47 | " // Setup ========================================================================", 48 | 49 | " vec2 uv = fragCoord.xy / uResolution.xy;", 50 | 51 | " float time = uGlobalTime * uSpeed;", 52 | 53 | " float directionalAdjustment = uFlipAnimationDirection > 0.0 ? -1.0 : 1.0;", 54 | 55 | 56 | " if (uSpeed > 0.0) {", 57 | 58 | " // Define Axis-Based Striping ===================================================", 59 | 60 | " float divisions = uDivisions;", 61 | " float effectPosition = fragCoord.y;", 62 | " float effectDimension = uResolution.y;", 63 | " if (uAnimateHorizontal > 0.0) {", 64 | " effectPosition = fragCoord.x;", 65 | " effectDimension = uResolution.x;", 66 | " divisions = floor((divisions * (uResolution.x / uResolution.y)) + 0.5);", 67 | " }", 68 | " float stripe = floor(effectPosition / (effectDimension / divisions));", 69 | 70 | 71 | " // Animate =====================================================================", 72 | 73 | " float timeAdjustedForStripe = time - ((uDivisionWidth / divisions) * stripe) * directionalAdjustment;", 74 | " float offsetAtTime = mod(timeAdjustedForStripe, 1.0);", 75 | 76 | " // Divide sin output by 2 and add to 0.5 so that sin wave move between 0.0 and 1.0 rather than -1.0 and 1.0.", 77 | " float easing = 0.5 + easingForPositionWithDeadzoneWidth(offsetAtTime, uDivisionWidth);", 78 | 79 | " // Mulptiply offsetAtTime by 2.0 and subtract from 1.0 so that position changes over a range of -1.0 to 1.0 rather than 0.0 to 1.0.", 80 | " if (uAnimateHorizontal > 0.0) {", 81 | " uv.x -= ((1.0 - (offsetAtTime * 2.0)) * easing) * directionalAdjustment;", 82 | " } else {", 83 | " uv.y -= ((1.0 - (offsetAtTime * 2.0)) * easing) * directionalAdjustment;", 84 | " }", 85 | " }", 86 | 87 | " mainImage = textTexture(uv);", 88 | 89 | "}" 90 | ].join("\n"); 91 | 92 | return mainImageSrc; 93 | } 94 | 95 | return { 96 | 97 | constructor : Blotter.SlidingDoorMaterial, 98 | 99 | init : function () { 100 | this.mainImage = _mainImageSrc(); 101 | this.uniforms = { 102 | uDivisions : { type : "1f", value : 5.0 }, 103 | uDivisionWidth : { type : "1f", value : 0.25 }, 104 | uAnimateHorizontal : { type : "1f", value : 0.0 }, 105 | uFlipAnimationDirection : { type : "1f", value : 0.0 }, 106 | uSpeed : { type : "1f", value : 0.2 } 107 | }; 108 | } 109 | }; 110 | 111 | })()); 112 | 113 | })( 114 | this.Blotter 115 | ); 116 | --------------------------------------------------------------------------------