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