├── .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 | ![Image Title](https://tympanus.net/codrops/wp-content/uploads/2021/03/circletext_featured.jpg) 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Frihed Kun Stadium Copenhagen  37 | 38 | 39 | Jægerspris Infinite X Playground  40 | 41 | 42 | Mit Valg Building Næstved  43 | 44 | 45 | Køge For Kærlighed Building VI  46 | 47 | 48 |
49 |

Circular Text Effect

50 | 55 | 60 |
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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | And now this tree of ours may grow tall in the woods  37 | 38 | 39 | Depth over distance every time  40 | 41 | 42 | But it's the roots that will bind us here  43 | 44 | 45 | Depth over distance was all I asked of you  46 | 47 | 48 |
49 |

Circular Text Effect

50 | 55 | 60 |
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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Let life begin I've cleansed all my sins  37 | 38 | 39 | Burn all the money absolve all the lies  40 | 41 | 42 | We are caged in simulations  43 | 44 | 45 | But something has changed in us  46 | 47 | 48 |
49 |

Circular Text Effect

50 | 55 | 60 |
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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Frihed Kun Stadium Copenhagen  33 | 34 | 35 | Jægerspris Infinite X Playground  36 | 37 | 38 | Mit Valg Building Næstved  39 | 40 | 41 | Køge For Kærlighed Building VI  42 | 43 | 44 |
45 |

Circular Text Effect

46 | 51 | 56 |
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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | And now this tree of ours may grow tall in the woods  33 | 34 | 35 | Depth over distance every time  36 | 37 | 38 | But it's the roots that will bind us here  39 | 40 | 41 | Depth over distance was all I asked of you  42 | 43 | 44 |
45 |

Circular Text Effect

46 | 51 | 56 |
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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Let life begin I've cleansed all my sins  33 | 34 | 35 | Burn all the money absolve all the lies  36 | 37 | 38 | We are caged in simulations  39 | 40 | 41 | But something has changed in us  42 | 43 | 44 |
45 |

Circular Text Effect

46 | 51 | 56 |
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 | }; --------------------------------------------------------------------------------