├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── 1.dc197a9a.jpg ├── base.98fd6c19.css ├── favicon.26242483.ico ├── index.html ├── js.00a46daa.css └── js.00a46daa.js ├── package.json └── src ├── css └── base.css ├── favicon.ico ├── img └── 1.jpg ├── index.html └── js ├── cursor.js ├── index.js └── utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 - 2020 [Codrops](https://tympanus.net/codrops) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typography Motion Effect 2 | 3 | The demo for the tutorial on how to recreate a letter stagger animation with GSAP and Splitting.js, based on [Thibaud Allie's](https://dribbble.com/thibaudallie) work as seen on [Dani Morales](http://www.danimorales-style.com/). 4 | 5 | ![Typography Motion](https://tympanus.net/codrops/wp-content/uploads/2020/06/TypographyMotion_featured.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=50136) 8 | 9 | [Demo](http://tympanus.net/Tutorials/TypographyMotion/) 10 | 11 | 12 | ## Installation 13 | 14 | Install dependencies: 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | Compile the code for development and start a local server: 21 | 22 | ``` 23 | npm start 24 | ``` 25 | 26 | Create the build: 27 | 28 | ``` 29 | npm run build 30 | ``` 31 | 32 | ## Credits 33 | 34 | - Recreation of the animation seen on http://www.danimorales-style.com/ designed by [Thibaud Allie](https://dribbble.com/thibaudallie). 35 | - Image by [Silviu Beniamin Tofan](https://unsplash.com/@tofansilviuben) from [Unsplash.com](https://unsplash.com/) 36 | 37 | ## Misc 38 | 39 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/) 40 | 41 | ## License 42 | [MIT](LICENSE) 43 | 44 | Made with :blue_heart: by [Codrops](http://www.codrops.com) 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /dist/1.dc197a9a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TypographyMotion/800c4b530783565b69619b8a9033e0ca069207ec/dist/1.dc197a9a.jpg -------------------------------------------------------------------------------- /dist/base.98fd6c19.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: #000; 14 | --color-bg: #faf5e3; 15 | --color-link: #8d561f; 16 | --color-link-hover: #000; 17 | color: var(--color-text); 18 | background-color: var(--color-bg); 19 | --cursor-stroke: none; 20 | --cursor-fill: #988c67; 21 | --cursor-stroke-width: 1px; 22 | font-family: tenon, sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | overflow: hidden; 26 | overflow-y: scroll; 27 | } 28 | 29 | /* Page Loader */ 30 | .js .loading::before, 31 | .js .loading::after { 32 | content: ''; 33 | position: fixed; 34 | z-index: 1000; 35 | } 36 | 37 | .js .loading::before { 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | background: var(--color-bg); 43 | } 44 | 45 | .js .loading::after { 46 | top: 50%; 47 | left: 50%; 48 | width: 60px; 49 | height: 60px; 50 | margin: -30px 0 0 -30px; 51 | border-radius: 50%; 52 | opacity: 0.4; 53 | background: var(--color-link); 54 | animation: loaderAnim 0.7s linear infinite alternate forwards; 55 | 56 | } 57 | 58 | @keyframes loaderAnim { 59 | to { 60 | opacity: 1; 61 | transform: scale3d(0.5,0.5,1); 62 | } 63 | } 64 | 65 | a { 66 | text-decoration: underline; 67 | color: var(--color-link); 68 | outline: none; 69 | cursor: pointer; 70 | } 71 | 72 | a:hover, 73 | a:focus { 74 | color: var(--color-link-hover); 75 | outline: none; 76 | text-decoration: none; 77 | } 78 | 79 | em { 80 | font-weight: 500; 81 | } 82 | 83 | .frame { 84 | padding: 3rem 5vw; 85 | text-align: center; 86 | position: relative; 87 | z-index: 1000; 88 | } 89 | 90 | .frame__title { 91 | font-size: 1rem; 92 | margin: 0 0 1rem; 93 | font-weight: normal; 94 | } 95 | 96 | .frame__links { 97 | margin: 0.35rem 0 0 0; 98 | } 99 | 100 | .frame__links a:not(:last-child) { 101 | margin-right: 1rem; 102 | } 103 | 104 | .frame__home { 105 | text-decoration: none; 106 | color: var(--color-text); 107 | display: inline-block; 108 | height: 1.75rem; 109 | margin: 1rem 0; 110 | } 111 | 112 | .frame__home-title { 113 | font-weight: 700; 114 | } 115 | 116 | .frame__home-sub { 117 | align-self: flex-end; 118 | margin-left: 0.5rem; 119 | } 120 | 121 | .frame__about { 122 | display: inline-block; 123 | margin: 1rem 0; 124 | font-weight: 700; 125 | text-decoration: none; 126 | color: var(--color-text); 127 | } 128 | 129 | .frame__about-item { 130 | display: none; 131 | } 132 | 133 | .frame__about-item--current { 134 | display: inline-block; 135 | } 136 | 137 | .content { 138 | display: grid; 139 | grid-template-columns: 100%; 140 | grid-template-areas: "content-item"; 141 | justify-content: center; 142 | position: relative; 143 | cursor: default; 144 | } 145 | 146 | .content__item { 147 | grid-area: content-item; 148 | opacity: 0; 149 | pointer-events: none; 150 | display: flex; 151 | align-content: center; 152 | flex-direction: column; 153 | padding: 0 1rem; 154 | overflow: hidden; 155 | height: 0; 156 | } 157 | 158 | .content__item--home { 159 | justify-content: center; 160 | min-height: 300px; 161 | } 162 | 163 | .content__item--current { 164 | overflow: visible; 165 | height: auto; 166 | opacity: 1; 167 | pointer-events: auto; 168 | } 169 | 170 | .content__item-credits { 171 | margin-top: auto; 172 | align-self: center; 173 | max-width: 260px; 174 | text-align: center; 175 | padding-bottom: 3rem; 176 | } 177 | 178 | .content__item-credits a { 179 | white-space: nowrap; 180 | } 181 | 182 | .content__paragraph { 183 | font-family: freight-big-pro, serif; 184 | font-size: 7vw; 185 | font-weight: 300; 186 | letter-spacing: -0.2vw; 187 | word-spacing: 1vw; 188 | text-transform: uppercase; 189 | margin: 0; 190 | line-height: 1; 191 | position: relative; 192 | overflow: hidden; 193 | flex: none; 194 | } 195 | 196 | .content__paragraph--first { 197 | margin-top: auto; 198 | } 199 | 200 | .content__paragraph--last { 201 | margin-bottom: auto; 202 | } 203 | 204 | .content__paragraph--large { 205 | font-size: 10vw; 206 | } 207 | 208 | .content__paragraph--right { 209 | align-self: flex-end; 210 | } 211 | 212 | .content__figure { 213 | margin: 0; 214 | max-width: 100%; 215 | margin-top: 0.5rem; 216 | position: relative; 217 | } 218 | 219 | .content__figure-img { 220 | max-width: 100%; 221 | } 222 | 223 | .content__figure-caption { 224 | margin: 1rem 0 0 0; 225 | font-size: 0.85rem; 226 | padding-bottom: 5rem; 227 | } 228 | 229 | .char { 230 | will-change: transform; 231 | } 232 | 233 | .cursor { 234 | display: none; 235 | } 236 | 237 | @media screen and (min-width: 53em) { 238 | .frame { 239 | position: absolute; 240 | text-align: left; 241 | top: 0; 242 | left: 0; 243 | display: grid; 244 | align-content: space-between; 245 | width: 100%; 246 | max-width: none; 247 | height: 100vh; 248 | padding: 3rem 8vw; 249 | pointer-events: none; 250 | grid-template-columns: 20% 60% 20%; 251 | grid-template-rows: auto auto auto; 252 | grid-template-areas: 'home title about' 253 | '... ... ...' 254 | '... ... credits'; 255 | } 256 | .frame__home { 257 | display: flex; 258 | grid-area: home; 259 | justify-content: flex-start; 260 | justify-self: start; 261 | margin: 0; 262 | } 263 | .frame__title-wrap { 264 | grid-area: title; 265 | text-align: center; 266 | } 267 | .frame__title { 268 | margin: 0; 269 | } 270 | .frame__links { 271 | margin: 1rem 0 0 0; 272 | } 273 | .frame__about { 274 | margin: 0; 275 | grid-area: about; 276 | align-self: start; 277 | justify-self: end; 278 | } 279 | .frame__credits { 280 | text-align: right; 281 | grid-area: credits; 282 | justify-self: end; 283 | } 284 | .frame a { 285 | pointer-events: auto; 286 | } 287 | .content__item { 288 | min-height: 100vh; 289 | padding: 12rem 8vw 3rem; 290 | } 291 | .content__title-inner--offset { 292 | margin-top: 3rem; 293 | } 294 | .content__title-seperator { 295 | margin: 0 2rem; 296 | } 297 | .content__item--home { 298 | max-height: none; 299 | } 300 | .content__figure { 301 | max-width: 48vw; 302 | justify-self: flex-end; 303 | margin-left: auto; 304 | margin-top: -5vw; 305 | } 306 | .content__figure-caption { 307 | position: absolute; 308 | right: 100%; 309 | bottom: 0; 310 | margin-right: 2rem; 311 | text-align: right; 312 | width: 50%; 313 | font-size: 1rem; 314 | padding: 0; 315 | } 316 | .content__item-credits { 317 | align-self: flex-end; 318 | max-width: 260px; 319 | text-align: right; 320 | padding: 0; 321 | } 322 | } 323 | 324 | @media (any-pointer: fine) { 325 | .cursor { 326 | position: fixed; 327 | top: 0; 328 | left: 0; 329 | display: block; 330 | pointer-events: none; 331 | } 332 | .cursor__inner { 333 | fill: var(--cursor-fill); 334 | stroke: var(--cursor-stroke); 335 | stroke-width: var(--cursor-stroke-width); 336 | opacity: 0.5; 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /dist/favicon.26242483.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TypographyMotion/800c4b530783565b69619b8a9033e0ca069207ec/dist/favicon.26242483.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Typography Motion Effect | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 |
26 |
27 | 28 | JF 29 | 2020 30 | 31 |
32 |

Typography Motion Effect

33 | 38 |
39 | 40 | About 41 | Close 42 | 43 |
44 |
45 |
46 |

Make Data

47 |

your strength

48 |
Recreation of the effect on Dani Morales by Thibaud Allie
49 |
50 |
51 |

Johnathan Flynn is

52 |

a Salt Lake City

53 |

based data and social

54 |

engineer

55 |
56 | Some image 57 |
We are here face to face with a crucial point in analytic realism. Realism argues that we have no alternative except either to regard analysis as falsifying, and thus commit ourselves to distrust of science as an organ of knowledge.
58 |
59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /dist/js.00a46daa.css: -------------------------------------------------------------------------------- 1 | /* Recommended styles for Splitting */ 2 | .splitting .word, 3 | .splitting .char { 4 | display: inline-block; 5 | } 6 | 7 | /* Psuedo-element chars */ 8 | .splitting .char { 9 | position: relative; 10 | } 11 | 12 | /** 13 | * Populate the psuedo elements with the character to allow for expanded effects 14 | * Set to `display: none` by default; just add `display: block` when you want 15 | * to use the psuedo elements 16 | */ 17 | .splitting .char::before, 18 | .splitting .char::after { 19 | content: attr(data-char); 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | visibility: hidden; 24 | transition: inherit; 25 | user-select: none; 26 | } 27 | 28 | /* Expanded CSS Variables */ 29 | 30 | .splitting { 31 | /* The center word index */ 32 | --word-center: calc((var(--word-total) - 1) / 2); 33 | 34 | /* The center character index */ 35 | --char-center: calc((var(--char-total) - 1) / 2); 36 | 37 | /* The center character index */ 38 | --line-center: calc((var(--line-total) - 1) / 2); 39 | } 40 | 41 | .splitting .word { 42 | /* Pecent (0-1) of the word's position */ 43 | --word-percent: calc(var(--word-index) / var(--word-total)); 44 | 45 | /* Pecent (0-1) of the line's position */ 46 | --line-percent: calc(var(--line-index) / var(--line-total)); 47 | } 48 | 49 | .splitting .char { 50 | /* Percent (0-1) of the char's position */ 51 | --char-percent: calc(var(--char-index) / var(--char-total)); 52 | 53 | /* Offset from center, positive & negative */ 54 | --char-offset: calc(var(--char-index) - var(--char-center)); 55 | 56 | /* Absolute distance from center, only positive */ 57 | --distance: calc( 58 | (var(--char-offset) * var(--char-offset)) / var(--char-center) 59 | ); 60 | 61 | /* Distance from center where -1 is the far left, 0 is center, 1 is far right */ 62 | --distance-sine: calc(var(--char-offset) / var(--char-center)); 63 | 64 | /* Distance from center where 1 is far left/far right, 0 is center */ 65 | --distance-percent: calc((var(--distance) / var(--char-center))); 66 | } 67 | .splitting.cells img { width: 100%; display: block; } 68 | 69 | @supports ( display: grid ) { 70 | .splitting.cells { 71 | position: relative; 72 | overflow: hidden; 73 | background-size: cover; 74 | visibility: hidden; 75 | } 76 | 77 | .splitting .cell-grid { 78 | background: inherit; 79 | position: absolute; 80 | top: 0; 81 | left: 0; 82 | width: 100%; 83 | height: 100%; 84 | display: grid; 85 | grid-template: repeat( var(--row-total), 1fr ) / repeat( var(--col-total), 1fr ); 86 | } 87 | 88 | .splitting .cell { 89 | background: inherit; 90 | position: relative; 91 | overflow: hidden; 92 | } 93 | 94 | .splitting .cell-inner { 95 | background: inherit; 96 | position: absolute; 97 | visibility: visible; 98 | /* Size to fit the whole container size */ 99 | width: calc(100% * var(--col-total)); 100 | height: calc(100% * var(--row-total)); 101 | /* Position properly */ 102 | left: calc(-100% * var(--col-index)); 103 | top: calc(-100% * var(--row-index)); 104 | } 105 | 106 | /* Helper variables for advanced effects */ 107 | .splitting .cell { 108 | --center-x: calc((var(--col-total) - 1) / 2); 109 | --center-y: calc((var(--row-total) - 1) / 2); 110 | 111 | /* Offset from center, positive & negative */ 112 | --offset-x: calc(var(--col-index) - var(--center-x)); 113 | --offset-y: calc(var(--row-index) - var(--center-y)); 114 | 115 | /* Absolute distance from center, only positive */ 116 | --distance-x: calc( (var(--offset-x) * var(--offset-x)) / var(--center-x) ); 117 | 118 | /* Absolute distance from center, only positive */ 119 | --distance-y: calc( (var(--offset-y) * var(--offset-y)) / var(--center-y) ); 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TypographyMotion", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel src/index.html --open", 8 | "clean": "rm -rf dist/*", 9 | "build:parcel": "parcel build src/index.html --no-content-hash --no-minify --no-source-maps --public-url ./", 10 | "build": "npm run clean && npm run build:parcel" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/codrops/TypographyMotionEffect.git" 15 | }, 16 | "keywords": [], 17 | "author": "Codrops", 18 | "license": "MIT", 19 | "homepage": "[HOMEPAGE]", 20 | "bugs": { 21 | "url": "https://github.com/codrops/TypographyMotionEffect/issues" 22 | }, 23 | "dependencies": { 24 | "gsap": "^3.3.3", 25 | "parcel-bundler": "^1.12.4", 26 | "splitting": "^1.0.6", 27 | "utils": "^0.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/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: #000; 14 | --color-bg: #faf5e3; 15 | --color-link: #8d561f; 16 | --color-link-hover: #000; 17 | color: var(--color-text); 18 | background-color: var(--color-bg); 19 | --cursor-stroke: none; 20 | --cursor-fill: #988c67; 21 | --cursor-stroke-width: 1px; 22 | font-family: tenon, sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | overflow: hidden; 26 | overflow-y: scroll; 27 | } 28 | 29 | /* Page Loader */ 30 | .js .loading::before, 31 | .js .loading::after { 32 | content: ''; 33 | position: fixed; 34 | z-index: 1000; 35 | } 36 | 37 | .js .loading::before { 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | background: var(--color-bg); 43 | } 44 | 45 | .js .loading::after { 46 | top: 50%; 47 | left: 50%; 48 | width: 60px; 49 | height: 60px; 50 | margin: -30px 0 0 -30px; 51 | border-radius: 50%; 52 | opacity: 0.4; 53 | background: var(--color-link); 54 | animation: loaderAnim 0.7s linear infinite alternate forwards; 55 | 56 | } 57 | 58 | @keyframes loaderAnim { 59 | to { 60 | opacity: 1; 61 | transform: scale3d(0.5,0.5,1); 62 | } 63 | } 64 | 65 | a { 66 | text-decoration: underline; 67 | color: var(--color-link); 68 | outline: none; 69 | cursor: pointer; 70 | } 71 | 72 | a:hover, 73 | a:focus { 74 | color: var(--color-link-hover); 75 | outline: none; 76 | text-decoration: none; 77 | } 78 | 79 | em { 80 | font-weight: 500; 81 | } 82 | 83 | .frame { 84 | padding: 3rem 5vw; 85 | text-align: center; 86 | position: relative; 87 | z-index: 1000; 88 | } 89 | 90 | .frame__title { 91 | font-size: 1rem; 92 | margin: 0 0 1rem; 93 | font-weight: normal; 94 | } 95 | 96 | .frame__links { 97 | margin: 0.35rem 0 0 0; 98 | } 99 | 100 | .frame__links a:not(:last-child) { 101 | margin-right: 1rem; 102 | } 103 | 104 | .frame__home { 105 | text-decoration: none; 106 | color: var(--color-text); 107 | display: inline-block; 108 | height: 1.75rem; 109 | margin: 1rem 0; 110 | } 111 | 112 | .frame__home-title { 113 | font-weight: 700; 114 | } 115 | 116 | .frame__home-sub { 117 | align-self: flex-end; 118 | margin-left: 0.5rem; 119 | } 120 | 121 | .frame__about { 122 | display: inline-block; 123 | margin: 1rem 0; 124 | font-weight: 700; 125 | text-decoration: none; 126 | color: var(--color-text); 127 | } 128 | 129 | .frame__about-item { 130 | display: none; 131 | } 132 | 133 | .frame__about-item--current { 134 | display: inline-block; 135 | } 136 | 137 | .content { 138 | display: grid; 139 | grid-template-columns: 100%; 140 | grid-template-areas: "content-item"; 141 | justify-content: center; 142 | position: relative; 143 | cursor: default; 144 | } 145 | 146 | .content__item { 147 | grid-area: content-item; 148 | opacity: 0; 149 | pointer-events: none; 150 | display: flex; 151 | align-content: center; 152 | flex-direction: column; 153 | padding: 0 1rem; 154 | overflow: hidden; 155 | height: 0; 156 | } 157 | 158 | .content__item--home { 159 | justify-content: center; 160 | min-height: 300px; 161 | } 162 | 163 | .content__item--current { 164 | overflow: visible; 165 | height: auto; 166 | opacity: 1; 167 | pointer-events: auto; 168 | } 169 | 170 | .content__item-credits { 171 | margin-top: auto; 172 | align-self: center; 173 | max-width: 260px; 174 | text-align: center; 175 | padding-bottom: 3rem; 176 | } 177 | 178 | .content__item-credits a { 179 | white-space: nowrap; 180 | } 181 | 182 | .content__paragraph { 183 | font-family: freight-big-pro, serif; 184 | font-size: 7vw; 185 | font-weight: 300; 186 | letter-spacing: -0.2vw; 187 | word-spacing: 1vw; 188 | text-transform: uppercase; 189 | margin: 0; 190 | line-height: 1; 191 | position: relative; 192 | overflow: hidden; 193 | flex: none; 194 | } 195 | 196 | .content__paragraph--first { 197 | margin-top: auto; 198 | } 199 | 200 | .content__paragraph--last { 201 | margin-bottom: auto; 202 | } 203 | 204 | .content__paragraph--large { 205 | font-size: 10vw; 206 | } 207 | 208 | .content__paragraph--right { 209 | align-self: flex-end; 210 | } 211 | 212 | .content__figure { 213 | margin: 0; 214 | max-width: 100%; 215 | margin-top: 0.5rem; 216 | position: relative; 217 | } 218 | 219 | .content__figure-img { 220 | max-width: 100%; 221 | } 222 | 223 | .content__figure-caption { 224 | margin: 1rem 0 0 0; 225 | font-size: 0.85rem; 226 | padding-bottom: 5rem; 227 | } 228 | 229 | .char { 230 | will-change: transform; 231 | } 232 | 233 | .cursor { 234 | display: none; 235 | } 236 | 237 | @media screen and (min-width: 53em) { 238 | .frame { 239 | position: absolute; 240 | text-align: left; 241 | top: 0; 242 | left: 0; 243 | display: grid; 244 | align-content: space-between; 245 | width: 100%; 246 | max-width: none; 247 | height: 100vh; 248 | padding: 3rem 8vw; 249 | pointer-events: none; 250 | grid-template-columns: 20% 60% 20%; 251 | grid-template-rows: auto auto auto; 252 | grid-template-areas: 'home title about' 253 | '... ... ...' 254 | '... ... credits'; 255 | } 256 | .frame__home { 257 | display: flex; 258 | grid-area: home; 259 | justify-content: flex-start; 260 | justify-self: start; 261 | margin: 0; 262 | } 263 | .frame__title-wrap { 264 | grid-area: title; 265 | text-align: center; 266 | } 267 | .frame__title { 268 | margin: 0; 269 | } 270 | .frame__links { 271 | margin: 1rem 0 0 0; 272 | } 273 | .frame__about { 274 | margin: 0; 275 | grid-area: about; 276 | align-self: start; 277 | justify-self: end; 278 | } 279 | .frame__credits { 280 | text-align: right; 281 | grid-area: credits; 282 | justify-self: end; 283 | } 284 | .frame a { 285 | pointer-events: auto; 286 | } 287 | .content__item { 288 | min-height: 100vh; 289 | padding: 12rem 8vw 3rem; 290 | } 291 | .content__title-inner--offset { 292 | margin-top: 3rem; 293 | } 294 | .content__title-seperator { 295 | margin: 0 2rem; 296 | } 297 | .content__item--home { 298 | max-height: none; 299 | } 300 | .content__figure { 301 | max-width: 48vw; 302 | justify-self: flex-end; 303 | margin-left: auto; 304 | margin-top: -5vw; 305 | } 306 | .content__figure-caption { 307 | position: absolute; 308 | right: 100%; 309 | bottom: 0; 310 | margin-right: 2rem; 311 | text-align: right; 312 | width: 50%; 313 | font-size: 1rem; 314 | padding: 0; 315 | } 316 | .content__item-credits { 317 | align-self: flex-end; 318 | max-width: 260px; 319 | text-align: right; 320 | padding: 0; 321 | } 322 | } 323 | 324 | @media (any-pointer: fine) { 325 | .cursor { 326 | position: fixed; 327 | top: 0; 328 | left: 0; 329 | display: block; 330 | pointer-events: none; 331 | } 332 | .cursor__inner { 333 | fill: var(--cursor-fill); 334 | stroke: var(--cursor-stroke); 335 | stroke-width: var(--cursor-stroke-width); 336 | opacity: 0.5; 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TypographyMotion/800c4b530783565b69619b8a9033e0ca069207ec/src/favicon.ico -------------------------------------------------------------------------------- /src/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/TypographyMotion/800c4b530783565b69619b8a9033e0ca069207ec/src/img/1.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Typography Motion Effect | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | JF 21 | 2020 22 | 23 |
24 |

Typography Motion Effect

25 | 30 |
31 | 32 | About 33 | Close 34 | 35 |
36 |
37 |
38 |

Make Data

39 |

your strength

40 |
Recreation of the effect on Dani Morales by Thibaud Allie
41 |
42 |
43 |

Johnathan Flynn is

44 |

a Salt Lake City

45 |

based data and social

46 |

engineer

47 |
48 | Some image 49 |
We are here face to face with a crucial point in analytic realism. Realism argues that we have no alternative except either to regard analysis as falsifying, and thus commit ourselves to distrust of science as an organ of knowledge.
50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/js/cursor.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { lerp, getMousePos } from './utils'; 3 | 4 | // Track the mouse position 5 | let mouse = {x: 0, y: 0}; 6 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 7 | 8 | export default class Cursor { 9 | constructor(el) { 10 | this.DOM = {el: el}; 11 | this.DOM.el.style.opacity = 0; 12 | 13 | this.bounds = this.DOM.el.getBoundingClientRect(); 14 | 15 | this.renderedStyles = { 16 | tx: {previous: 0, current: 0, amt: 0.18}, 17 | ty: {previous: 0, current: 0, amt: 0.18} 18 | }; 19 | 20 | this.onMouseMoveEv = () => { 21 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 22 | this.renderedStyles.ty.previous = this.renderedStyles.ty.previous = mouse.y - this.bounds.height/2; 23 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 24 | requestAnimationFrame(() => this.render()); 25 | window.removeEventListener('mousemove', this.onMouseMoveEv); 26 | }; 27 | window.addEventListener('mousemove', this.onMouseMoveEv); 28 | } 29 | render() { 30 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 31 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 32 | 33 | for (const key in this.renderedStyles ) { 34 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 35 | } 36 | 37 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px)`; 38 | 39 | requestAnimationFrame(() => this.render()); 40 | } 41 | } -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import "splitting/dist/splitting.css"; 2 | import "splitting/dist/splitting-cells.css"; 3 | import Splitting from "splitting"; 4 | import { gsap } from 'gsap'; 5 | import { preloadFonts } from './utils'; 6 | import Cursor from "./cursor"; 7 | 8 | // Preload typekit fonts 9 | preloadFonts('lwc3axy').then(() => document.body.classList.remove('loading')); 10 | 11 | // Call the splittingjs to transform the data-splitting texts to spans of chars 12 | Splitting(); 13 | 14 | // Custom cursor 15 | new Cursor(document.querySelector('.cursor')) 16 | 17 | // DOM elements 18 | let DOM = { 19 | content: { 20 | home: { 21 | section: document.querySelector('.content__item--home'), 22 | get chars() { 23 | return this.section.querySelectorAll('.content__paragraph .word > .char, .whitespace'); 24 | }, 25 | isVisible: true 26 | }, 27 | about: { 28 | section: document.querySelector('.content__item--about'), 29 | get chars() { 30 | return this.section.querySelectorAll('.content__paragraph .word > .char, .whitespace') 31 | }, 32 | get picture() { 33 | return this.section.querySelector('.content__figure'); 34 | }, 35 | isVisible: false 36 | } 37 | }, 38 | links: { 39 | about: { 40 | anchor: document.querySelector('a.frame__about'), 41 | get stateElement() { 42 | return this.anchor.children; 43 | } 44 | }, 45 | home: document.querySelector('a.frame__home') 46 | } 47 | }; 48 | 49 | // The gsap timeline (and some default settings) where the magic happens 50 | const timelineSettings = { 51 | staggerValue: 0.014, 52 | charsDuration: 0.5 53 | }; 54 | const timeline = gsap.timeline({paused: true}) 55 | .addLabel('start') 56 | // Stagger the animation of the home section chars 57 | .staggerTo( DOM.content.home.chars, timelineSettings.charsDuration, { 58 | ease: 'Power3.easeIn', 59 | y: '-100%', 60 | opacity: 0 61 | }, timelineSettings.staggerValue, 'start') 62 | // Here we do the switch 63 | // We need to toggle the current class for the content sections 64 | .addLabel('switchtime') 65 | .add( () => { 66 | DOM.content.home.section.classList.toggle('content__item--current'); 67 | DOM.content.about.section.classList.toggle('content__item--current'); 68 | }) 69 | // Change the body's background color 70 | .to(document.body, { 71 | duration: 0.8, 72 | ease: 'Power1.easeInOut', 73 | backgroundColor: '#c3b996' 74 | }, 'switchtime-=timelineSettings.charsDuration/4') 75 | // Start values for the about section elements that will animate in 76 | .set(DOM.content.about.chars, { 77 | y: '100%' 78 | }, 'switchtime') 79 | .set(DOM.content.about.picture, { 80 | y: '40%', 81 | rotation: -4, 82 | opacity: 0 83 | }, 'switchtime') 84 | // Stagger the animation of the about section chars 85 | .staggerTo( DOM.content.about.chars, timelineSettings.charsDuration, { 86 | ease: 'Power3.easeOut', 87 | y: '0%' 88 | }, timelineSettings.staggerValue, 'switchtime') 89 | // Finally, animate the picture in 90 | .to( DOM.content.about.picture, 0.8, { 91 | ease: 'Power3.easeOut', 92 | y: '0%', 93 | opacity: 1, 94 | rotation: 0 95 | }, 'switchtime+=0.6'); 96 | 97 | // Clicking the about and homepage links will toggle the content area, by playing and reversing the timeline 98 | // Need to switch current state for the about/close links 99 | const switchContent = () => { 100 | DOM.links.about.stateElement[0].classList[DOM.content.about.isVisible ? 'add' : 'remove']('frame__about-item--current'); 101 | DOM.links.about.stateElement[1].classList[DOM.content.about.isVisible ? 'remove' : 'add']('frame__about-item--current'); 102 | timeline[DOM.content.about.isVisible ? 'reverse' : 'play'](); 103 | DOM.content.about.isVisible = !DOM.content.about.isVisible; 104 | DOM.content.home.isVisible = !DOM.content.about.isVisible; 105 | }; 106 | 107 | DOM.links.about.anchor.addEventListener('click', () => switchContent()); 108 | DOM.links.home.addEventListener('click', () => { 109 | if ( DOM.content.home.isVisible ) return; 110 | switchContent(); 111 | }); -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | // Linear interpolation 2 | const lerp = (a, b, n) => (1 - n) * a + n * b; 3 | 4 | // Gets the mouse position 5 | const getMousePos = e => { 6 | return { 7 | x : e.clientX, 8 | y : e.clientY 9 | }; 10 | }; 11 | 12 | // Preload images 13 | const preloadFonts = (id) => { 14 | return new Promise((resolve, reject) => { 15 | WebFont.load({ 16 | typekit: { 17 | id: id 18 | }, 19 | active: resolve 20 | }); 21 | }); 22 | }; 23 | 24 | export { lerp, getMousePos, preloadFonts }; 25 | --------------------------------------------------------------------------------