├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── dist
├── base.98fd6c19.css
├── demo1.151408fb.js
├── demo2.44794d1a.js
├── demo3.b516845c.js
├── favicon.26242483.ico
├── index.html
├── index2.html
└── index3.html
├── package.json
└── src
├── css
└── base.css
├── favicon.ico
├── index.html
├── index2.html
├── index3.html
└── js
├── demo1
├── index.js
└── intro.js
├── demo2
├── index.js
└── intro.js
├── demo3
├── index.js
└── intro.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 - 2021 [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 | # Circular Text Effect
2 |
3 | An experimental circular SVG text animation inspired by this [concept](https://twitter.com/hi_twotwentytwo/status/1159907278096650240?s=03)
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/?p=53487)
8 |
9 | [Demo](http://tympanus.net/Development/CircularTextEffect/)
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 | ## Misc
33 |
34 | 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/)
35 |
36 | ## License
37 | [MIT](LICENSE)
38 |
39 | Made with :blue_heart: by [Codrops](http://www.codrops.com)
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/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: #a5a5a5;
14 | --color-bg: #191613;
15 | --color-link: #d6af7c;
16 | --color-link-hover: #fff;
17 | --color-text-circle-1: #48423c;
18 | --color-text-circle-2: #48423c;
19 | --color-text-circle-3: #48423c;
20 | --color-text-circle-4: #48423c;
21 | --font-circle-1: ivymode, sans-serif;
22 | --font-weight-circle-1: 300;
23 | --font-circle-2: modesto-condensed, serif;
24 | --font-weight-circle-2: 400;
25 | --font-circle-3: minerva-modern, sans-serif;
26 | --font-weight-circle-3: 400;
27 | --font-circle-4: niagara, serif;
28 | --font-weight-circle-4: 300;
29 | --color-button: #d6ae7c;
30 | --color-text-button: #1d1812;
31 | --dim-button: 90px;
32 | color: var(--color-text);
33 | background-color: var(--color-bg);
34 | font-family: tenon, sans-serif;
35 | -webkit-font-smoothing: antialiased;
36 | -moz-osx-font-smoothing: grayscale;
37 | }
38 |
39 | .demo-2 {
40 | --color-text: #a5a5a5;
41 | --color-bg: #000000;
42 | --color-link: #ea34a6;
43 | --color-link-hover: #fff;
44 | --color-font-circle: #7b7772;
45 | --color-button: #ea34a6;
46 | --color-text-button: #000000;
47 | --color-text-circle-1: #ea34a6;
48 | --color-text-circle-2: #422163;
49 | --color-text-circle-3: #212044;
50 | --color-text-circle-4: #292725;
51 | --font-circle-1: niagara, serif;
52 | --font-weight-circle-1: 300;
53 | --font-circle-2: ambroise-firmin-std, serif;
54 | --font-weight-circle-2: 300;
55 | --font-circle-3: modesto-condensed, serif;
56 | --font-weight-circle-3: 400;
57 | --font-circle-4: niagara, serif;
58 | --font-weight-circle-4: 300;
59 | }
60 |
61 | .demo-3 {
62 | --color-text: #292725;
63 | --color-bg: #dad4d1;
64 | --color-link: #a99c90;
65 | --color-link-hover: #292725;
66 | --color-font-circle: #7b7772;
67 | --color-button: #a99c90;
68 | --color-text-button: #272524;
69 | --color-text-circle-1: #272524;
70 | --color-text-circle-2: #272524;
71 | --color-text-circle-3: #272524;
72 | --color-text-circle-4: #272524;
73 | --font-circle-1: modesto-condensed, serif;
74 | --font-weight-circle-1: 400;
75 | --font-circle-2: ambroise-firmin-std, serif;
76 | --font-weight-circle-2: 300;
77 | --font-circle-3: modesto-condensed, serif;
78 | --font-weight-circle-3: 400;
79 | --font-circle-4: niagara, sans-serif;
80 | --font-weight-circle-4: 300;
81 | }
82 |
83 | /* Page Loader */
84 | .js body::before,
85 | .js body::after {
86 | content: '';
87 | position: fixed;
88 | z-index: 1000;
89 | transition: opacity 0.3s;
90 | opacity: 0;
91 | pointer-events: none;
92 | }
93 |
94 | .js body::before {
95 | top: 0;
96 | left: 0;
97 | width: 100%;
98 | height: 100%;
99 | background: var(--color-bg);
100 | }
101 |
102 | .js body::after {
103 | content: 'Loading';
104 | top: 0;
105 | left: 0;
106 | width: 100%;
107 | height: 100%;
108 | display: flex;
109 | align-items: center;
110 | justify-content: center;
111 | font-weight: 500;
112 | line-height: 1;
113 | color: var(--color-link);
114 | }
115 |
116 | .js .loading::before,
117 | .js .loading::after {
118 | opacity: 1;
119 | pointer-events: auto;
120 | }
121 |
122 | a {
123 | text-decoration: none;
124 | color: var(--color-link);
125 | outline: none;
126 | }
127 |
128 | a:hover {
129 | color: var(--color-link-hover);
130 | outline: none;
131 | }
132 |
133 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
134 | a:focus {
135 | /* Provide a fallback style for browsers
136 | that don't support :focus-visible */
137 | outline: none;
138 | background: lightgrey;
139 | }
140 |
141 | a:focus:not(:focus-visible) {
142 | /* Remove the focus indicator on mouse-focus for browsers
143 | that do support :focus-visible */
144 | background: transparent;
145 | }
146 |
147 | a:focus-visible {
148 | /* Draw a very noticeable focus style for
149 | keyboard-focus on browsers that do support
150 | :focus-visible */
151 | outline: 2px solid red;
152 | background: transparent;
153 | }
154 |
155 | .frame {
156 | padding: 3rem 5vw;
157 | text-align: center;
158 | position: relative;
159 | font-weight: 500;
160 | will-change: transform;
161 | }
162 |
163 | .js .frame {
164 | opacity: 0;
165 | }
166 |
167 | .frame__title {
168 | font-size: 1rem;
169 | margin: 0 0 1rem;
170 | font-weight: 500;
171 | }
172 |
173 | .frame__links {
174 | display: inline;
175 | }
176 |
177 | .frame__links a:not(:last-child),
178 | .frame__demos a:not(:last-child) {
179 | margin: 0 1rem 0 0;
180 | }
181 |
182 | .frame__demos {
183 | margin: 1rem 0;
184 | }
185 |
186 | .frame__demo--current,
187 | .frame__demo--current:hover {
188 | color: var(--color-text);
189 | }
190 |
191 | .enter {
192 | display: block;
193 | border: 0;
194 | width: var(--dim-button);
195 | height: var(--dim-button);
196 | position: absolute;
197 | left: calc(50% - var(--dim-button) / 2 );
198 | top: calc(50% - var(--dim-button) / 2 );
199 | font: inherit;
200 | font-weight: 500;
201 | cursor: pointer;
202 | background: none;
203 | color: var(--color-text-button);
204 | padding: 0;
205 | }
206 |
207 | .enter:focus,
208 | .enter:hover,
209 | .enter:active {
210 | outline: none;
211 | }
212 |
213 | .enter__bg {
214 | position: absolute;
215 | top: 0;
216 | left: 0;
217 | width: 100%;
218 | height: 100%;
219 | border-radius: 50%;
220 | background: var(--color-button);
221 | }
222 |
223 | .enter__text {
224 | position: relative;
225 | }
226 |
227 | .js .enter {
228 | opacity: 0;
229 | }
230 |
231 | .content {
232 | display: flex;
233 | flex-direction: column;
234 | width: 100vw;
235 | padding: 2rem;
236 | height: calc(100vh - 13rem);
237 | position: relative;
238 | justify-content: flex-start;
239 | align-items: center;
240 | text-align: center;
241 | }
242 |
243 | .js .content {
244 | opacity: 0;
245 | }
246 |
247 | .content p {
248 | font-size: 2rem;
249 | font-size: clamp(1.25rem,6vw,2.15rem);
250 | line-height: 1.25;
251 | max-width: 900px;
252 | margin: 0;
253 | pointer-events: none;
254 | will-change: transform;
255 | }
256 |
257 | .circles {
258 | pointer-events: none;
259 | position: fixed;
260 | --dim: 186vmin;
261 | width: var(--dim);
262 | height: var(--dim);
263 | top: calc(50% - var(--dim) / 2 );
264 | left: calc(50% - var(--dim) / 2 );
265 | }
266 |
267 | .demo-3 .circles {
268 | --dim: 206vmin;
269 | }
270 |
271 | .circles__text {
272 | text-transform: uppercase;
273 | transform-origin: 700px 700px;
274 | will-change: transform, opacity;
275 | }
276 |
277 | .circles__text--1 {
278 | font-size: 25vmin;
279 | font-size: clamp(170px,25vmin,180px);
280 | font-family: var(--font-circle-1);
281 | font-weight: var(--font-weight-circle-1);
282 | fill: var(--color-text-circle-1);
283 | }
284 |
285 | .circles__text--2 {
286 | font-size: 17vmin;
287 | font-size: clamp(136px,17vmin,153px);
288 | font-family: var(--font-circle-2);
289 | font-weight: var(--font-weight-circle-2);
290 | fill: var(--color-text-circle-2);
291 | }
292 |
293 | .circles__text--3 {
294 | font-size: 13.5vmin;
295 | font-size: clamp(110px,13.5vmin,120px);
296 | font-family: var(--font-circle-3);
297 | font-weight: var(--font-weight-circle-3);
298 | fill: var(--color-text-circle-3);
299 | }
300 |
301 | .circles__text--4 {
302 | font-size: 9.5vmin;
303 | font-size: clamp(85px,9.5vmin,94px);
304 | font-family: var(--font-circle-4);
305 | font-weight: var(--font-weight-circle-4);
306 | fill: var(--color-text-circle-4);
307 | }
308 |
309 | @media screen and (min-width: 53em) {
310 | .frame {
311 | position: fixed;
312 | text-align: left;
313 | z-index: 100;
314 | top: 0;
315 | left: 0;
316 | display: grid;
317 | align-content: space-between;
318 | width: 100%;
319 | max-width: none;
320 | height: 100vh;
321 | padding: 2rem;
322 | pointer-events: none;
323 | grid-template-columns: 50% 50%;
324 | grid-template-rows: auto auto auto;
325 | grid-template-areas: 'title links'
326 | '... ...'
327 | '... demos';
328 | }
329 | .frame__title {
330 | margin: 0;
331 | grid-area: title;
332 | }
333 | .frame__menu {
334 | grid-area: menu;
335 | justify-self: end;
336 | }
337 | .frame__links {
338 | grid-area: links;
339 | padding: 0;
340 | justify-self: end;
341 | }
342 | .frame__demos {
343 | margin: 0;
344 | grid-area: demos;
345 | justify-self: end;
346 | }
347 | .frame a {
348 | pointer-events: auto;
349 | }
350 | .content {
351 | height: 100vh;
352 | justify-content: center;
353 | text-align: left;
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/dist/favicon.26242483.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/CircularTextEffect/fcd9aaa5d55d0d6ce5f9d1b6795e4c3d45dd93b2/dist/favicon.26242483.ico
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 1 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
48 |
61 |
62 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
63 |
64 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/dist/index2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 2 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
48 |
61 |
62 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
63 |
64 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/dist/index3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 3 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
48 |
61 |
62 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
63 |
64 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CircularTextEffect",
3 | "version": "1.0.0",
4 | "description": "*How to use this template:*",
5 | "main": "src/js/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/[NAME].git"
15 | },
16 | "keywords": [],
17 | "author": "Codrops",
18 | "license": "MIT",
19 | "homepage": "http://[HOMEPAGE]",
20 | "bugs": {
21 | "url": "https://github.com/codrops/[NAME]/issues"
22 | },
23 | "dependencies": {
24 | "gsap": "^3.6.0",
25 | "parcel-bundler": "^1.12.4",
26 | "splitting": "^1.0.6"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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: #a5a5a5;
14 | --color-bg: #191613;
15 | --color-link: #d6af7c;
16 | --color-link-hover: #fff;
17 | --color-text-circle-1: #48423c;
18 | --color-text-circle-2: #48423c;
19 | --color-text-circle-3: #48423c;
20 | --color-text-circle-4: #48423c;
21 | --font-circle-1: ivymode, sans-serif;
22 | --font-weight-circle-1: 300;
23 | --font-circle-2: modesto-condensed, serif;
24 | --font-weight-circle-2: 400;
25 | --font-circle-3: minerva-modern, sans-serif;
26 | --font-weight-circle-3: 400;
27 | --font-circle-4: niagara, serif;
28 | --font-weight-circle-4: 300;
29 | --color-button: #d6ae7c;
30 | --color-text-button: #1d1812;
31 | --dim-button: 90px;
32 | color: var(--color-text);
33 | background-color: var(--color-bg);
34 | font-family: tenon, sans-serif;
35 | -webkit-font-smoothing: antialiased;
36 | -moz-osx-font-smoothing: grayscale;
37 | }
38 |
39 | .demo-2 {
40 | --color-text: #a5a5a5;
41 | --color-bg: #000000;
42 | --color-link: #ea34a6;
43 | --color-link-hover: #fff;
44 | --color-font-circle: #7b7772;
45 | --color-button: #ea34a6;
46 | --color-text-button: #000000;
47 | --color-text-circle-1: #ea34a6;
48 | --color-text-circle-2: #422163;
49 | --color-text-circle-3: #212044;
50 | --color-text-circle-4: #292725;
51 | --font-circle-1: niagara, serif;
52 | --font-weight-circle-1: 300;
53 | --font-circle-2: ambroise-firmin-std, serif;
54 | --font-weight-circle-2: 300;
55 | --font-circle-3: modesto-condensed, serif;
56 | --font-weight-circle-3: 400;
57 | --font-circle-4: niagara, serif;
58 | --font-weight-circle-4: 300;
59 | }
60 |
61 | .demo-3 {
62 | --color-text: #292725;
63 | --color-bg: #dad4d1;
64 | --color-link: #a99c90;
65 | --color-link-hover: #292725;
66 | --color-font-circle: #7b7772;
67 | --color-button: #a99c90;
68 | --color-text-button: #272524;
69 | --color-text-circle-1: #272524;
70 | --color-text-circle-2: #272524;
71 | --color-text-circle-3: #272524;
72 | --color-text-circle-4: #272524;
73 | --font-circle-1: modesto-condensed, serif;
74 | --font-weight-circle-1: 400;
75 | --font-circle-2: ambroise-firmin-std, serif;
76 | --font-weight-circle-2: 300;
77 | --font-circle-3: modesto-condensed, serif;
78 | --font-weight-circle-3: 400;
79 | --font-circle-4: niagara, sans-serif;
80 | --font-weight-circle-4: 300;
81 | }
82 |
83 | /* Page Loader */
84 | .js body::before,
85 | .js body::after {
86 | content: '';
87 | position: fixed;
88 | z-index: 1000;
89 | transition: opacity 0.3s;
90 | opacity: 0;
91 | pointer-events: none;
92 | }
93 |
94 | .js body::before {
95 | top: 0;
96 | left: 0;
97 | width: 100%;
98 | height: 100%;
99 | background: var(--color-bg);
100 | }
101 |
102 | .js body::after {
103 | content: 'Loading';
104 | top: 0;
105 | left: 0;
106 | width: 100%;
107 | height: 100%;
108 | display: flex;
109 | align-items: center;
110 | justify-content: center;
111 | font-weight: 500;
112 | line-height: 1;
113 | color: var(--color-link);
114 | }
115 |
116 | .js .loading::before,
117 | .js .loading::after {
118 | opacity: 1;
119 | pointer-events: auto;
120 | }
121 |
122 | a {
123 | text-decoration: none;
124 | color: var(--color-link);
125 | outline: none;
126 | }
127 |
128 | a:hover {
129 | color: var(--color-link-hover);
130 | outline: none;
131 | }
132 |
133 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
134 | a:focus {
135 | /* Provide a fallback style for browsers
136 | that don't support :focus-visible */
137 | outline: none;
138 | background: lightgrey;
139 | }
140 |
141 | a:focus:not(:focus-visible) {
142 | /* Remove the focus indicator on mouse-focus for browsers
143 | that do support :focus-visible */
144 | background: transparent;
145 | }
146 |
147 | a:focus-visible {
148 | /* Draw a very noticeable focus style for
149 | keyboard-focus on browsers that do support
150 | :focus-visible */
151 | outline: 2px solid red;
152 | background: transparent;
153 | }
154 |
155 | .frame {
156 | padding: 3rem 5vw;
157 | text-align: center;
158 | position: relative;
159 | font-weight: 500;
160 | will-change: transform;
161 | }
162 |
163 | .js .frame {
164 | opacity: 0;
165 | }
166 |
167 | .frame__title {
168 | font-size: 1rem;
169 | margin: 0 0 1rem;
170 | font-weight: 500;
171 | }
172 |
173 | .frame__links {
174 | display: inline;
175 | }
176 |
177 | .frame__links a:not(:last-child),
178 | .frame__demos a:not(:last-child) {
179 | margin: 0 1rem 0 0;
180 | }
181 |
182 | .frame__demos {
183 | margin: 1rem 0;
184 | }
185 |
186 | .frame__demo--current,
187 | .frame__demo--current:hover {
188 | color: var(--color-text);
189 | }
190 |
191 | .enter {
192 | display: block;
193 | border: 0;
194 | width: var(--dim-button);
195 | height: var(--dim-button);
196 | position: absolute;
197 | left: calc(50% - var(--dim-button) / 2 );
198 | top: calc(50% - var(--dim-button) / 2 );
199 | font: inherit;
200 | font-weight: 500;
201 | cursor: pointer;
202 | background: none;
203 | color: var(--color-text-button);
204 | padding: 0;
205 | }
206 |
207 | .enter:focus,
208 | .enter:hover,
209 | .enter:active {
210 | outline: none;
211 | }
212 |
213 | .enter__bg {
214 | position: absolute;
215 | top: 0;
216 | left: 0;
217 | width: 100%;
218 | height: 100%;
219 | border-radius: 50%;
220 | background: var(--color-button);
221 | }
222 |
223 | .enter__text {
224 | position: relative;
225 | }
226 |
227 | .js .enter {
228 | opacity: 0;
229 | }
230 |
231 | .content {
232 | display: flex;
233 | flex-direction: column;
234 | width: 100vw;
235 | padding: 2rem;
236 | height: calc(100vh - 13rem);
237 | position: relative;
238 | justify-content: flex-start;
239 | align-items: center;
240 | text-align: center;
241 | }
242 |
243 | .js .content {
244 | opacity: 0;
245 | }
246 |
247 | .content p {
248 | font-size: 2rem;
249 | font-size: clamp(1.25rem,6vw,2.15rem);
250 | line-height: 1.25;
251 | max-width: 900px;
252 | margin: 0;
253 | pointer-events: none;
254 | will-change: transform;
255 | }
256 |
257 | .circles {
258 | pointer-events: none;
259 | position: fixed;
260 | --dim: 186vmin;
261 | width: var(--dim);
262 | height: var(--dim);
263 | top: calc(50% - var(--dim) / 2 );
264 | left: calc(50% - var(--dim) / 2 );
265 | }
266 |
267 | .demo-3 .circles {
268 | --dim: 206vmin;
269 | }
270 |
271 | .circles__text {
272 | text-transform: uppercase;
273 | transform-origin: 700px 700px;
274 | will-change: transform, opacity;
275 | }
276 |
277 | .circles__text--1 {
278 | font-size: 25vmin;
279 | font-size: clamp(170px,25vmin,180px);
280 | font-family: var(--font-circle-1);
281 | font-weight: var(--font-weight-circle-1);
282 | fill: var(--color-text-circle-1);
283 | }
284 |
285 | .circles__text--2 {
286 | font-size: 17vmin;
287 | font-size: clamp(136px,17vmin,153px);
288 | font-family: var(--font-circle-2);
289 | font-weight: var(--font-weight-circle-2);
290 | fill: var(--color-text-circle-2);
291 | }
292 |
293 | .circles__text--3 {
294 | font-size: 13.5vmin;
295 | font-size: clamp(110px,13.5vmin,120px);
296 | font-family: var(--font-circle-3);
297 | font-weight: var(--font-weight-circle-3);
298 | fill: var(--color-text-circle-3);
299 | }
300 |
301 | .circles__text--4 {
302 | font-size: 9.5vmin;
303 | font-size: clamp(85px,9.5vmin,94px);
304 | font-family: var(--font-circle-4);
305 | font-weight: var(--font-weight-circle-4);
306 | fill: var(--color-text-circle-4);
307 | }
308 |
309 | @media screen and (min-width: 53em) {
310 | .frame {
311 | position: fixed;
312 | text-align: left;
313 | z-index: 100;
314 | top: 0;
315 | left: 0;
316 | display: grid;
317 | align-content: space-between;
318 | width: 100%;
319 | max-width: none;
320 | height: 100vh;
321 | padding: 2rem;
322 | pointer-events: none;
323 | grid-template-columns: 50% 50%;
324 | grid-template-rows: auto auto auto;
325 | grid-template-areas: 'title links'
326 | '... ...'
327 | '... demos';
328 | }
329 | .frame__title {
330 | margin: 0;
331 | grid-area: title;
332 | }
333 | .frame__menu {
334 | grid-area: menu;
335 | justify-self: end;
336 | }
337 | .frame__links {
338 | grid-area: links;
339 | padding: 0;
340 | justify-self: end;
341 | }
342 | .frame__demos {
343 | margin: 0;
344 | grid-area: demos;
345 | justify-self: end;
346 | }
347 | .frame a {
348 | pointer-events: auto;
349 | }
350 | .content {
351 | height: 100vh;
352 | justify-content: center;
353 | text-align: left;
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codrops/CircularTextEffect/fcd9aaa5d55d0d6ce5f9d1b6795e4c3d45dd93b2/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 1 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
44 |
57 |
58 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
59 |
60 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/index2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 2 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
44 |
57 |
58 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
59 |
60 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/index3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Circular Text Effect | Demo 3 | Codrops
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
44 |
57 |
58 |
We are a creative agency that focuses on human-centric design and ergonomic workplace innovations.
59 |
60 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/js/demo1/index.js:
--------------------------------------------------------------------------------
1 | import { preloadFonts } from '../utils';
2 | import { Intro } from './intro';
3 |
4 | const intro = new Intro(document.querySelector('.circles'));
5 |
6 | // Preload images and fonts
7 | Promise.all([preloadFonts('kxo3pgz')]).then(() => {
8 | // remove loader (loading class)
9 | document.body.classList.remove('loading');
10 | // start intro animation
11 | intro.start();
12 | });
13 |
--------------------------------------------------------------------------------
/src/js/demo1/intro.js:
--------------------------------------------------------------------------------
1 | import { randomNumber } from '../utils';
2 | import { gsap } from 'gsap';
3 |
4 | const DOM = {
5 | frame: document.querySelector('.frame'),
6 | content: document.querySelector('.content'),
7 | enterCtrl: document.querySelector('.enter'),
8 | enterBackground: document.querySelector('.enter__bg')
9 | };
10 |
11 | export class Intro {
12 | constructor(el) {
13 | // the SVG element
14 | this.DOM = {el: el};
15 | // SVG texts
16 | this.DOM.circleText = [...this.DOM.el.querySelectorAll('text.circles__text')];
17 | // total of texts
18 | this.circleTextTotal = this.DOM.circleText.length;
19 | // initial setudp
20 | this.setup();
21 | }
22 | setup() {
23 | // need to set the transform origin in the center
24 | gsap.set(this.DOM.circleText, { transformOrigin: '50% 50%' });
25 | // hide on start
26 | gsap.set([this.DOM.circleText, DOM.content.children, DOM.frame.children], {opacity: 0});
27 | // don't allow to hover
28 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
29 |
30 | this.initEvents();
31 | }
32 | initEvents() {
33 | // click and hover events for the "enter" button:
34 | this.enterMouseEnterEv = () => {
35 | gsap.killTweensOf([DOM.enterBackground,this.DOM.circleText]);
36 |
37 | gsap.to(DOM.enterBackground, {
38 | duration: 0.8,
39 | ease: 'power4',
40 | scale: 1.2,
41 | opacity: 1
42 | });
43 |
44 | gsap.to(this.DOM.circleText, {
45 | duration: 4,
46 | ease: 'power4',
47 | rotate: '+=180',
48 | stagger: {
49 | amount: -0.3
50 | }
51 | });
52 | };
53 | this.enterMouseLeaveEv = () => {
54 | //gsap.killTweensOf(DOM.enterBackground);
55 | gsap.to(DOM.enterBackground, {
56 | duration: 0.8,
57 | ease: 'power4',
58 | scale: 1
59 | });
60 | };
61 | this.enterClickEv = () => this.enter();
62 |
63 | DOM.enterCtrl.addEventListener('mouseenter', this.enterMouseEnterEv);
64 | DOM.enterCtrl.addEventListener('mouseleave', this.enterMouseLeaveEv);
65 | DOM.enterCtrl.addEventListener('click', this.enterClickEv);
66 | }
67 | // initial (intro) animation
68 | start() {
69 | this.startTL = gsap.timeline()
70 | .addLabel('start', 0)
71 | // scale in the texts & enter button and fade them in
72 | .to([this.DOM.circleText, DOM.enterCtrl], {
73 | duration: 2.5,
74 | ease: 'expo',
75 | startAt: {opacity: 0, scale: 0.3},
76 | scale: 1,
77 | opacity: 1,
78 | stagger: {
79 | amount: 0.5
80 | }
81 | }, 'start')
82 | // at start+1 allow the hover over the enter ctrl
83 | .add(() => gsap.set(DOM.enterCtrl, {pointerEvents: 'auto'}), 'start+=1');
84 | }
85 | // animation when clicking the enter button
86 | enter() {
87 | // stop the previous timeline
88 | this.startTL.kill();
89 | // remove any event listener on the button
90 | DOM.enterCtrl.removeEventListener('mouseenter', this.enterMouseEnterEv);
91 | DOM.enterCtrl.removeEventListener('mouseleave', this.enterMouseLeaveEv);
92 | DOM.enterCtrl.removeEventListener('click', this.enterClickEv);
93 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
94 | // show frame and content
95 | gsap.set([DOM.frame, DOM.content], {opacity: 1});
96 | // start the animation
97 | gsap.timeline()
98 | .addLabel('start', 0)
99 | .to(DOM.enterCtrl, {
100 | duration: 1.5,
101 | ease: 'expo.inOut',
102 | scale: 0.7,
103 | opacity: 0
104 | }, 'start')
105 | .to(this.DOM.circleText, {
106 | duration: 1.5,
107 | ease: 'expo.inOut',
108 | scale: i => 1.5+(this.circleTextTotal-i)*.3,
109 | opacity: 0,
110 | stagger: {
111 | amount: 0.2
112 | }
113 | }, 'start')
114 | // show the content elements
115 | .to([DOM.content.children, DOM.frame.children], {
116 | duration: 1,
117 | ease: 'power3.out',
118 | startAt: {opacity: 0, scale: 0.9},
119 | scale: 1,
120 | opacity: 1,
121 | stagger: {
122 | amount: 0.3
123 | }
124 | }, 'start+=1.1')
125 | }
126 | }
--------------------------------------------------------------------------------
/src/js/demo2/index.js:
--------------------------------------------------------------------------------
1 | import { preloadFonts } from '../utils';
2 | import { Intro } from './intro';
3 |
4 | const intro = new Intro(document.querySelector('.circles'));
5 |
6 | // Preload images and fonts
7 | Promise.all([preloadFonts('kxo3pgz')]).then(() => {
8 | // remove loader (loading class)
9 | document.body.classList.remove('loading');
10 | // start intro
11 | intro.start();
12 | });
13 |
--------------------------------------------------------------------------------
/src/js/demo2/intro.js:
--------------------------------------------------------------------------------
1 | import { randomNumber } from '../utils';
2 | import { gsap } from 'gsap';
3 |
4 | const DOM = {
5 | frame: document.querySelector('.frame'),
6 | content: document.querySelector('.content'),
7 | enterCtrl: document.querySelector('.enter'),
8 | enterBackground: document.querySelector('.enter__bg')
9 | };
10 |
11 | export class Intro {
12 | constructor(el) {
13 | // the SVG element
14 | this.DOM = {el: el};
15 | // SVG texts
16 | this.DOM.circleText = [...this.DOM.el.querySelectorAll('text.circles__text')];
17 | // total
18 | this.circleTextTotal = this.DOM.circleText.length;
19 |
20 | this.setup();
21 | }
22 | setup() {
23 | // need to set the transform origin
24 | // need to set the transform origin in the center
25 | gsap.set(this.DOM.circleText, { transformOrigin: '50% 50%' });
26 | // hide on start
27 | gsap.set([this.DOM.circleText, DOM.content.children, DOM.frame.children], {opacity: 0});
28 | // don't allow to hover
29 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
30 |
31 | this.initEvents();
32 | }
33 | initEvents() {
34 | this.enterMouseEnterEv = () => {
35 | gsap.killTweensOf([DOM.enterBackground,this.DOM.circleText]);
36 |
37 | gsap.to(DOM.enterBackground, {
38 | duration: 1,
39 | ease: 'expo',
40 | scale: 1.4
41 | });
42 | gsap.to(this.DOM.circleText, {
43 | duration: 1,
44 | ease: 'expo',
45 | scale: 1.15,
46 | rotation: i => i%2 ? '-=90' : '+=90',
47 | opacity: 0.4,
48 |
49 | });
50 | };
51 | this.enterMouseLeaveEv = () => {
52 | // gsap.killTweensOf([DOM.enterBackground,this.DOM.circleText]);
53 | gsap.to(DOM.enterBackground, {
54 | duration: 1,
55 | ease: 'expo',
56 | scale: 1
57 | });
58 | gsap.to(this.DOM.circleText, {
59 | duration: 1,
60 | ease: 'expo',
61 | scale: 1,
62 | rotation: i => i%2 ? '+=120' : '-=120',
63 | opacity: 1,
64 | stagger: {
65 | amount: -0.2
66 | }
67 | });
68 | };
69 | DOM.enterCtrl.addEventListener('mouseenter', this.enterMouseEnterEv);
70 | DOM.enterCtrl.addEventListener('mouseleave', this.enterMouseLeaveEv);
71 |
72 | this.enterClickEv = () => this.enter();
73 | DOM.enterCtrl.addEventListener('click', this.enterClickEv);
74 | }
75 | start() {
76 | this.startTL = gsap.timeline()
77 | .addLabel('start', 0)
78 | // rotation for all texts
79 | .to(this.DOM.circleText, {
80 | duration: 3,
81 | ease: 'expo.inOut',
82 | rotation: i => i%2 ? 90 : -90,
83 | stagger: {
84 | amount: 0.4
85 | }
86 | }, 'start')
87 | // scale in the texts & enter button and fade them in
88 | .to([this.DOM.circleText, DOM.enterCtrl], {
89 | duration: 3,
90 | ease: 'expo.inOut',
91 | startAt: {opacity: 0, scale: 0.8},
92 | scale: 1,
93 | opacity: 1,
94 | stagger: {
95 | amount: 0.4
96 | }
97 | }, 'start')
98 | // at start+1 allow the hover over the enter ctrl
99 | .add(() => {
100 | gsap.set(DOM.enterCtrl, {pointerEvents: 'auto'});
101 | }, 'start+=2');
102 | }
103 | enter() {
104 | this.startTL.kill();
105 |
106 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
107 | DOM.enterCtrl.removeEventListener('mouseenter', this.enterMouseEnterEv);
108 | DOM.enterCtrl.removeEventListener('mouseleave', this.enterMouseLeaveEv);
109 |
110 | gsap.set([DOM.frame, DOM.content], {opacity: 1});
111 |
112 | gsap.timeline()
113 | .addLabel('start', 0)
114 | .to(DOM.enterCtrl, {
115 | duration: 0.6,
116 | ease: 'back.in',
117 | scale: 0.2,
118 | opacity: 0
119 | }, 'start')
120 | .to(this.DOM.circleText, {
121 | duration: 0.8,
122 | ease: 'back.in',
123 | scale: 0,
124 | opacity: 0,
125 | stagger: {
126 | amount: -0.4
127 | }
128 | }, 'start')
129 | .to([DOM.content.children, DOM.frame.children], {
130 | duration: 0.9,
131 | ease: 'back.out',
132 | startAt: {opacity: 0, scale: 1.2},
133 | scale: 1,
134 | opacity: 1,
135 | stagger: {
136 | amount: 0.3
137 | }
138 | }, 'start+=1.3')
139 | }
140 | }
--------------------------------------------------------------------------------
/src/js/demo3/index.js:
--------------------------------------------------------------------------------
1 | import { preloadFonts } from '../utils';
2 | import { Intro } from './intro';
3 |
4 | const intro = new Intro(document.querySelector('.circles'));
5 |
6 | // Preload images and fonts
7 | Promise.all([preloadFonts('kxo3pgz')]).then(() => {
8 | // remove loader (loading class)
9 | document.body.classList.remove('loading');
10 | // start intro
11 | intro.start();
12 | });
13 |
--------------------------------------------------------------------------------
/src/js/demo3/intro.js:
--------------------------------------------------------------------------------
1 | import { randomNumber } from '../utils';
2 | import { gsap } from 'gsap';
3 |
4 | const DOM = {
5 | frame: document.querySelector('.frame'),
6 | content: document.querySelector('.content'),
7 | enterCtrl: document.querySelector('.enter'),
8 | enterBackground: document.querySelector('.enter__bg')
9 | };
10 |
11 | export class Intro {
12 | constructor(el) {
13 | // the SVG element
14 | this.DOM = {el: el};
15 | // SVG texts
16 | this.DOM.circleText = [...this.DOM.el.querySelectorAll('text.circles__text')];
17 | // total
18 | this.circleTextTotal = this.DOM.circleText.length;
19 |
20 | this.setup();
21 | }
22 | setup() {
23 | // need to set the transform origin in the center
24 | gsap.set(this.DOM.circleText, { transformOrigin: '50% 50%' });
25 | // hide on start
26 | gsap.set([this.DOM.circleText, DOM.content.children, DOM.frame.children], {opacity: 0});
27 | // don't allow to hover
28 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
29 |
30 | this.initEvents();
31 | }
32 | initEvents() {
33 | this.enterMouseEnterEv = () => {
34 | gsap.killTweensOf([DOM.enterBackground,this.DOM.circleText]);
35 |
36 | gsap.to(DOM.enterBackground, {
37 | duration: 1.3,
38 | ease: 'expo',
39 | scale: 1.4
40 | });
41 | gsap.to(this.DOM.circleText, {
42 | duration: 0.5,
43 | ease: 'expo',
44 | rotation: '+=120',
45 | scale: 0.5,
46 | opacity: 0.2,
47 | stagger: {
48 | amount: -0.15
49 | }
50 | });
51 | };
52 | this.enterMouseLeaveEv = () => {
53 | //gsap.killTweensOf([DOM.enterBackground,this.DOM.circleText]);
54 |
55 | gsap.to(DOM.enterBackground, {
56 | duration: 2,
57 | ease: 'elastic.out(1, 0.4)',
58 | scale: 1
59 | });
60 | gsap.to(this.DOM.circleText, {
61 | duration: 2,
62 | ease: 'elastic.out(1, 0.4)',
63 | scale: 1,
64 | rotation: '-=120',
65 | opacity: 1,
66 | stagger: {
67 | amount: 0.15
68 | }
69 | });
70 | };
71 | DOM.enterCtrl.addEventListener('mouseenter', this.enterMouseEnterEv);
72 | DOM.enterCtrl.addEventListener('mouseleave', this.enterMouseLeaveEv);
73 |
74 | this.enterClickEv = () => this.enter();
75 | DOM.enterCtrl.addEventListener('click', this.enterClickEv);
76 | }
77 | start() {
78 | this.startTL = gsap.timeline()
79 | .addLabel('start', 0)
80 | // rotation for all texts
81 | .to(this.DOM.circleText, {
82 | duration: 3,
83 | ease: 'expo.inOut',
84 | rotation: 90,
85 | stagger: {
86 | amount: 0.4
87 | }
88 | }, 'start')
89 | // scale in the texts & enter button and fade them in
90 | .to([this.DOM.circleText, DOM.enterCtrl], {
91 | duration: 3,
92 | ease: 'expo.inOut',
93 | startAt: {opacity: 0, scale: 0.8},
94 | scale: 1,
95 | opacity: 1,
96 | stagger: {
97 | amount: 0.4
98 | }
99 | }, 'start')
100 | // at start+1 allow the hover over the enter ctrl
101 | .add(() => {
102 | gsap.set(DOM.enterCtrl, {pointerEvents: 'auto'});
103 | }, 'start+=2');
104 | }
105 | enter() {
106 | this.startTL.kill();
107 |
108 | DOM.enterCtrl.removeEventListener('mouseenter', this.enterMouseEnterEv);
109 | DOM.enterCtrl.removeEventListener('mouseleave', this.enterMouseLeaveEv);
110 | gsap.set(DOM.enterCtrl, {pointerEvents: 'none'});
111 |
112 | gsap.set([DOM.frame, DOM.content], {opacity: 1});
113 |
114 | gsap.timeline()
115 | .addLabel('start', 0)
116 | .to(DOM.enterCtrl, {
117 | duration: 0.6,
118 | ease: 'back.in',
119 | scale: 0.2,
120 | opacity: 0
121 | }, 'start')
122 | .to(this.DOM.circleText, {
123 | duration: 0.8,
124 | ease: 'back.in',
125 | scale: 1.6,
126 | opacity: 0,
127 | rotation: '-=20',
128 | stagger: {
129 | amount: 0.3
130 | }
131 | }, 'start')
132 | .to([DOM.content.children, DOM.frame.children], {
133 | duration: 0.8,
134 | ease: 'back.out',
135 | startAt: {opacity: 0, scale: 0.8},
136 | scale: 1,
137 | opacity: 1,
138 | stagger: {
139 | amount: 0.2
140 | }
141 | }, 'start+=1')
142 | }
143 | }
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | // Preload fonts
2 | const preloadFonts = (id) => {
3 | return new Promise((resolve) => {
4 | WebFont.load({
5 | typekit: {
6 | id: id
7 | },
8 | active: resolve
9 | });
10 | });
11 | };
12 |
13 | const randomNumber = (min,max) => Math.floor(Math.random() * (max - min + 1) + min);
14 |
15 | export {
16 | preloadFonts,
17 | randomNumber
18 | };
--------------------------------------------------------------------------------