├── .DS_Store ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── 1.dc197a9a.jpg ├── 2.3ca6bb44.jpg ├── demo1.ea7e4db3.css ├── demo1.ea7e4db3.css.map ├── demo1.ea7e4db3.js ├── demo1.ea7e4db3.js.map ├── demo2.06b37b5f.css ├── demo2.06b37b5f.css.map ├── demo2.06b37b5f.js ├── demo2.06b37b5f.js.map ├── demo3.f904c581.css ├── demo3.f904c581.css.map ├── demo3.f904c581.js ├── demo3.f904c581.js.map ├── demo4.7fb5a017.css ├── demo4.7fb5a017.css.map ├── demo4.7fb5a017.js ├── demo4.7fb5a017.js.map ├── favicon.26242483.ico ├── index.html ├── index2.242c2f2d.js ├── index2.242c2f2d.js.map ├── index2.html ├── index2.html.map ├── index2.js ├── index2.js.map ├── index3.02479077.js ├── index3.02479077.js.map ├── index3.html ├── index4.c87507a4.js ├── index4.c87507a4.js.map ├── index4.html ├── js.00a46daa.js ├── js.00a46daa.js.map ├── messapia-bold-webfont.3267b3d0.woff2 └── messapia-bold-webfont.6ddf630f.woff ├── package.json └── src ├── .DS_Store ├── .gitignore ├── css ├── demo1.css ├── demo2.css ├── demo3.css └── demo4.css ├── favicon.ico ├── fonts ├── messapia-bold-webfont.woff └── messapia-bold-webfont.woff2 ├── img ├── 1.jpg └── 2.jpg ├── index.html ├── index2.html ├── index3.html ├── index4.html └── js ├── cursor.js ├── cursor2.js ├── cursor3.js ├── cursor4.js ├── index.js ├── index2.js ├── index3.js ├── index4.js └── utils.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/.DS_Store -------------------------------------------------------------------------------- /.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 | # Animated Custom Cursor Effect 2 | 3 | Some distortion animations for custom cursors using SVG filters. 4 | 5 | ![Animated Custom Cursor Effect](https://tympanus.net/codrops/wp-content/uploads/2020/03/AnimatedCursor_featured-1.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=48725) 8 | 9 | [Demo](http://tympanus.net/Development/AnimatedCustomCursor/) 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 | - Images from [Unsplash](https://unsplash.com/) 35 | - Messapia font by Luca Marsano from [Collletttivo.it](http://collletttivo.it/) 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/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/dist/1.dc197a9a.jpg -------------------------------------------------------------------------------- /dist/2.3ca6bb44.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/dist/2.3ca6bb44.jpg -------------------------------------------------------------------------------- /dist/demo1.ea7e4db3.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #c3654e; 14 | --color-bg: #f2e9e0; 15 | --color-link: #000; 16 | --color-link-hover: #040203; 17 | --color-content: #040203; 18 | --font-alt: freight-display-pro, serif; 19 | color: var(--color-text); 20 | background-color: var(--color-bg); 21 | font-family: soleil, sans-serif; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | /* Cursor styles */ 25 | --cursor-stroke: #c3654e; 26 | --cursor-fill: none; 27 | --cursor-stroke-width: 1px; 28 | } 29 | 30 | a { 31 | text-decoration: none; 32 | color: var(--color-link); 33 | outline: none; 34 | cursor: pointer; 35 | } 36 | 37 | a:hover, 38 | a:focus { 39 | color: var(--color-link-hover); 40 | outline: none; 41 | } 42 | 43 | .frame { 44 | padding: 3rem 5vw; 45 | text-align: center; 46 | position: relative; 47 | z-index: 1000; 48 | } 49 | 50 | .frame__title { 51 | font-size: 1rem; 52 | margin: 0 0 1rem; 53 | font-weight: inherit; 54 | } 55 | 56 | .frame__links { 57 | display: block; 58 | } 59 | 60 | .frame__links > *:not(:last-child), 61 | .frame__demos > *:not(:last-child) { 62 | margin-right: 1rem; 63 | } 64 | 65 | .frame__demos { 66 | margin: 1rem 0; 67 | } 68 | 69 | .frame__demos span { 70 | font-weight: bold; 71 | color: var(--color-link); 72 | } 73 | 74 | .frame__initials { 75 | font-family: var(--font-alt); 76 | font-size: 1.5rem; 77 | pointer-events: none; 78 | line-height: 18px; 79 | } 80 | 81 | .content { 82 | display: flex; 83 | position: relative; 84 | flex-direction: column; 85 | width: 100vw; 86 | height: calc(100vh - 13rem); 87 | position: relative; 88 | justify-content: flex-start; 89 | align-items: center; 90 | } 91 | 92 | .content p { 93 | max-width: 80vw; 94 | font-size: 4vw; 95 | text-transform: uppercase; 96 | color: var(--color-content); 97 | font-family: var(--font-alt); 98 | font-weight: 300; 99 | line-height: 1.4; 100 | } 101 | 102 | .content p a { 103 | font-weight: 600; 104 | font-style: italic; 105 | position: relative; 106 | } 107 | 108 | .content p a::after { 109 | content: ''; 110 | position: absolute; 111 | width: 100%; 112 | height: 1px; 113 | background: currentColor; 114 | bottom: 0.5vw; 115 | left: 0; 116 | opacity: 0; 117 | transform-origin: 0% 50%; 118 | transform: scaleX(0); 119 | transition: all 0.25s ease-out; 120 | } 121 | 122 | .content p a:hover::after, 123 | .content p a:focus::after { 124 | opacity: 1; 125 | transform: scaleX(1); 126 | } 127 | 128 | .menu { 129 | display: flex; 130 | overflow: hidden; 131 | position: relative; 132 | width: 100vw; 133 | justify-content: center; 134 | } 135 | 136 | .menu__item { 137 | font-size: 6.5vw; 138 | margin: 0 2vw; 139 | cursor: default; 140 | font-family: var(--font-alt); 141 | color: var(--color-menu-link); 142 | cursor: pointer; 143 | } 144 | 145 | .menu__item:hover, 146 | .menu__item:focus { 147 | color: var(--color-menu-link-hover); 148 | } 149 | 150 | .cursor { 151 | display: none; 152 | } 153 | 154 | @media screen and (min-width: 53em) { 155 | .frame { 156 | position: fixed; 157 | text-align: left; 158 | z-index: 10000; 159 | top: 0; 160 | left: 0; 161 | display: grid; 162 | align-content: space-between; 163 | width: 100%; 164 | max-width: none; 165 | height: 100vh; 166 | padding: 2.5rem 3rem; 167 | pointer-events: none; 168 | grid-template-columns: 40% 20% 40%; 169 | grid-template-rows: auto auto auto; 170 | grid-template-areas: 'title initials links' 171 | '... ... ...' 172 | '... demos ...'; 173 | } 174 | .frame__title { 175 | grid-area: title; 176 | margin: 0; 177 | } 178 | .frame__initials { 179 | grid-area: initials; 180 | margin: 0; 181 | justify-self: center; 182 | align-self: center; 183 | } 184 | .frame__demos { 185 | margin: 0; 186 | grid-area: demos; 187 | justify-self: center; 188 | } 189 | .frame__links { 190 | grid-area: links; 191 | padding: 0; 192 | justify-self: end; 193 | } 194 | .frame__info { 195 | grid-area: info; 196 | margin: 0; 197 | justify-self: end; 198 | } 199 | .frame a { 200 | pointer-events: auto; 201 | } 202 | .content { 203 | height: 100vh; 204 | justify-content: center; 205 | } 206 | } 207 | 208 | @media (any-pointer: fine) { 209 | .cursor { 210 | position: fixed; 211 | top: 0; 212 | left: 0; 213 | display: block; 214 | pointer-events: none; 215 | opacity: 0; 216 | } 217 | .cursor__inner { 218 | fill: var(--cursor-fill); 219 | stroke: var(--cursor-stroke); 220 | stroke-width: var(--cursor-stroke-width); 221 | } 222 | } 223 | 224 | 225 | /*# sourceMappingURL=/demo1.ea7e4db3.css.map */ -------------------------------------------------------------------------------- /dist/demo1.ea7e4db3.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["css/demo1.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"demo1.ea7e4db3.css","sourceRoot":"../src","sourcesContent":["*,\n*::after,\n*::before {\n\tbox-sizing: border-box;\n}\n\n:root {\n\tfont-size: 15px;\n}\n\nbody {\n\tmargin: 0;\n\t--color-text: #c3654e;\n\t--color-bg: #f2e9e0;\n\t--color-link: #000;\n\t--color-link-hover: #040203;\n\t--color-content: #040203;\n\t--font-alt: freight-display-pro, serif;\n\tcolor: var(--color-text);\n\tbackground-color: var(--color-bg);\n\tfont-family: soleil, sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\t/* Cursor styles */\n\t--cursor-stroke: #c3654e;\n\t--cursor-fill: none;\n\t--cursor-stroke-width: 1px;\n}\n\na {\n\ttext-decoration: none;\n\tcolor: var(--color-link);\n\toutline: none;\n\tcursor: pointer;\n}\n\na:hover,\na:focus {\n\tcolor: var(--color-link-hover);\n\toutline: none;\n}\n\n.frame {\n\tpadding: 3rem 5vw;\n\ttext-align: center;\n\tposition: relative;\n\tz-index: 1000;\n}\n\n.frame__title {\n\tfont-size: 1rem;\n\tmargin: 0 0 1rem;\n\tfont-weight: inherit;\n}\n\n.frame__links {\n\tdisplay: block;\n}\n\n.frame__links > *:not(:last-child),\n.frame__demos > *:not(:last-child) {\n margin-right: 1rem;\n}\n\n.frame__demos {\n\tmargin: 1rem 0;\n}\n\n.frame__demos span {\n\tfont-weight: bold;\n\tcolor: var(--color-link);\n}\n\n.frame__initials {\n\tfont-family: var(--font-alt);\n\tfont-size: 1.5rem;\n\tpointer-events: none;\n\tline-height: 18px;\n}\n\n.content {\n\tdisplay: flex;\n\tposition: relative;\n\tflex-direction: column;\n\twidth: 100vw;\n\theight: calc(100vh - 13rem);\n\tposition: relative;\n\tjustify-content: flex-start;\n\talign-items: center;\n}\n\n.content p {\n\tmax-width: 80vw;\n\tfont-size: 4vw;\n\ttext-transform: uppercase;\n\tcolor: var(--color-content);\n\tfont-family: var(--font-alt);\n\tfont-weight: 300;\n\tline-height: 1.4;\n}\n\n.content p a {\n\tfont-weight: 600;\n\tfont-style: italic;\n\tposition: relative;\n}\n\n.content p a::after { \n\tcontent: ''; \n\tposition: absolute; \n\twidth: 100%; \n\theight: 1px; \n\tbackground: currentColor; \n\tbottom: 0.5vw; \n\tleft: 0; \n\topacity: 0; \n\ttransform-origin: 0% 50%; \n\ttransform: scaleX(0); \n\ttransition: all 0.25s ease-out; \n} \n \n.content p a:hover::after, \n.content p a:focus::after { \n\topacity: 1; \n\ttransform: scaleX(1); \n}\n\n.menu {\n\tdisplay: flex;\n\toverflow: hidden;\n\tposition: relative;\n\twidth: 100vw;\n\tjustify-content: center;\n}\n\n.menu__item {\n\tfont-size: 6.5vw;\n\tmargin: 0 2vw;\n\tcursor: default;\n\tfont-family: var(--font-alt);\n\tcolor: var(--color-menu-link);\n\tcursor: pointer;\n}\n\n.menu__item:hover,\n.menu__item:focus {\n\tcolor: var(--color-menu-link-hover);\n}\n\n.cursor {\n\tdisplay: none;\n}\n\n@media screen and (min-width: 53em) {\n\t.frame {\n\t\tposition: fixed;\n\t\ttext-align: left;\n\t\tz-index: 10000;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tdisplay: grid;\n\t\talign-content: space-between;\n\t\twidth: 100%;\n\t\tmax-width: none;\n\t\theight: 100vh;\n\t\tpadding: 2.5rem 3rem;\n\t\tpointer-events: none;\n\t\tgrid-template-columns: 40% 20% 40%;\n\t\tgrid-template-rows: auto auto auto;\n\t\tgrid-template-areas: 'title initials links'\n\t\t\t\t\t\t\t'... ... ...'\n\t\t\t\t\t\t\t'... demos ...';\n\t}\n\t.frame__title {\n\t\tgrid-area: title;\n\t\tmargin: 0;\n\t}\n\t.frame__initials {\n\t\tgrid-area: initials;\n\t\tmargin: 0;\n\t\tjustify-self: center;\n\t align-self: center;\n\t}\n\t.frame__demos {\n\t\tmargin: 0;\n\t\tgrid-area: demos;\n\t\tjustify-self: center;\n\t}\n\t.frame__links {\n\t\tgrid-area: links;\n\t\tpadding: 0;\n\t\tjustify-self: end;\n\t}\n\t.frame__info {\n\t\tgrid-area: info;\n\t\tmargin: 0;\n\t\tjustify-self: end;\n\t}\n\t.frame a {\n\t\tpointer-events: auto;\n\t}\n\t.content {\n\t\theight: 100vh;\n\t\tjustify-content: center;\n\t}\n}\n\n@media (any-pointer: fine) {\n\t.cursor {\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tdisplay: block;\n\t\tpointer-events: none;\n\t\topacity: 0;\n\t}\n\t.cursor__inner {\n\t\tfill: var(--cursor-fill);\n\t\tstroke: var(--cursor-stroke);\n\t\tstroke-width: var(--cursor-stroke-width);\n\t}\n}\n"]} -------------------------------------------------------------------------------- /dist/demo1.ea7e4db3.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requires ] 3 | // 4 | // map of requires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the require for previous bundles 8 | parcelRequire = (function (modules, cache, entry, globalName) { 9 | // Save the require from previous bundle to this closure if any 10 | var previousRequire = typeof parcelRequire === 'function' && parcelRequire; 11 | var nodeRequire = typeof require === 'function' && require; 12 | 13 | function newRequire(name, jumped) { 14 | if (!cache[name]) { 15 | if (!modules[name]) { 16 | // if we cannot find the module within our internal map or 17 | // cache jump to the current global require ie. the last bundle 18 | // that was added to the page. 19 | var currentRequire = typeof parcelRequire === 'function' && parcelRequire; 20 | if (!jumped && currentRequire) { 21 | return currentRequire(name, true); 22 | } 23 | 24 | // If there are other bundles on this page the require from the 25 | // previous one is saved to 'previousRequire'. Repeat this as 26 | // many times as there are bundles until the module is found or 27 | // we exhaust the require chain. 28 | if (previousRequire) { 29 | return previousRequire(name, true); 30 | } 31 | 32 | // Try the node require function if it exists. 33 | if (nodeRequire && typeof name === 'string') { 34 | return nodeRequire(name); 35 | } 36 | 37 | var err = new Error('Cannot find module \'' + name + '\''); 38 | err.code = 'MODULE_NOT_FOUND'; 39 | throw err; 40 | } 41 | 42 | localRequire.resolve = resolve; 43 | localRequire.cache = {}; 44 | 45 | var module = cache[name] = new newRequire.Module(name); 46 | 47 | modules[name][0].call(module.exports, localRequire, module, module.exports, this); 48 | } 49 | 50 | return cache[name].exports; 51 | 52 | function localRequire(x){ 53 | return newRequire(localRequire.resolve(x)); 54 | } 55 | 56 | function resolve(x){ 57 | return modules[name][1][x] || x; 58 | } 59 | } 60 | 61 | function Module(moduleName) { 62 | this.id = moduleName; 63 | this.bundle = newRequire; 64 | this.exports = {}; 65 | } 66 | 67 | newRequire.isParcelRequire = true; 68 | newRequire.Module = Module; 69 | newRequire.modules = modules; 70 | newRequire.cache = cache; 71 | newRequire.parent = previousRequire; 72 | newRequire.register = function (id, exports) { 73 | modules[id] = [function (require, module) { 74 | module.exports = exports; 75 | }, {}]; 76 | }; 77 | 78 | var error; 79 | for (var i = 0; i < entry.length; i++) { 80 | try { 81 | newRequire(entry[i]); 82 | } catch (e) { 83 | // Save first error but execute all entries 84 | if (!error) { 85 | error = e; 86 | } 87 | } 88 | } 89 | 90 | if (entry.length) { 91 | // Expose entry point to Node, AMD or browser globals 92 | // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js 93 | var mainExports = newRequire(entry[entry.length - 1]); 94 | 95 | // CommonJS 96 | if (typeof exports === "object" && typeof module !== "undefined") { 97 | module.exports = mainExports; 98 | 99 | // RequireJS 100 | } else if (typeof define === "function" && define.amd) { 101 | define(function () { 102 | return mainExports; 103 | }); 104 | 105 | // 22 | 23 | 24 |
25 |
26 |

Animated Custom Cursor Effect

27 | 31 |

C

32 |
33 | 1 34 | 2 35 | 3 36 | 4 37 |
38 |
39 |
40 |

Hi, my name is Eddie and I am a pragmatic UI designer who has worked with dozens of businesses and some non-profit organizations. If you'd like me to help you with your next UI project, let's talk!

41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /dist/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 28 | 29 | 30 |
31 |
32 |

Animated Custom Cursor Effect

33 | 37 |

C

38 |
39 | 1 40 | 2 41 | 3 42 | 4 43 |
44 |
45 |
46 | 51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /dist/index2.html.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../node_modules/parcel/src/builtins/bundle-url.js","../node_modules/parcel/src/builtins/css-loader.js"],"names":["bundleURL","getBundleURLCached","getBundleURL","Error","err","matches","stack","match","getBaseURL","url","replace","exports","bundle","require","updateLink","link","newLink","cloneNode","onload","remove","href","split","Date","now","parentNode","insertBefore","nextSibling","cssTimeout","reloadCSS","setTimeout","links","document","querySelectorAll","i","length","module"],"mappings":"AAAA,ACAA,IDAIA,ACAAY,MAAM,GDAG,ACAAC,GDAG,IAAhB,ACAoB,CAAC,cAAD,CAApB;;ADCA,ACCA,SDDSZ,ACCAa,UAAT,CAAoBC,IAApB,EAA0B,CDD1B,GAA8B;AAC5B,ACCA,MDDI,ACCAC,CDDChB,MCCM,GDDX,ACCce,EDDE,ECCE,CAACE,SAAL,EAAd;ADAEjB,IAAAA,SAAS,GAAGE,YAAY,EAAxB;AACD,ACADc,EAAAA,OAAO,CAACE,MAAR,GAAiB,YAAY;AAC3BH,IAAAA,IAAI,CAACI,MAAL;ADCF,ACAC,GAFD,MDEOnB,SAAP;AACD;ACACgB,EAAAA,OAAO,CAACI,IAAR,GAAeL,IAAI,CAACK,IAAL,CAAUC,KAAV,CAAgB,GAAhB,EAAqB,CAArB,IAA0B,GAA1B,GAAgCC,IAAI,CAACC,GAAL,EAA/C;ADEF,ACDER,EAAAA,IAAI,CAACS,EDCEtB,QCDP,CAAgBuB,GDClB,GAAwB,MCDtB,CAA6BT,OAA7B,EAAsCD,IAAI,CAACW,WAA3C;ADEA,ACDD;ADEC,MAAI;AACF,ACDJ,IAAIC,MDCM,IAAIxB,ACDA,GAAG,EDCP,EAAN,ACDJ;ADEG,GAFD,CAEE,OAAOC,GAAP,EAAY;AACZ,ACFJ,QDEQC,CCFCuB,MDEM,GAAG,ACFlB,CDEmB,ECFE,GDEGxB,GAAG,CAACE,KAAV,EAAiBC,KAAjB,CAAuB,+DAAvB,CAAd;ACDF,MAAIoB,UAAJ,EAAgB;ADEd,ACDA,QDCItB,OAAJ,EAAa;AACX,ACDH,aDCUG,UAAU,CAACH,OAAO,CAAC,CAAD,CAAR,CAAjB;AACD;AACF,ACDDsB,EAAAA,UAAU,GAAGE,UAAU,CAAC,YAAY;AAClC,QAAIC,KAAK,GAAGC,QAAQ,CAACC,gBAAT,CAA0B,wBAA1B,CAAZ;ADEF,SAAO,GAAP;AACD,ACFG,SAAK,IAAIC,CAAC,GAAG,CAAb,EAAgBA,CAAC,GAAGH,KAAK,CAACI,MAA1B,EAAkCD,CAAC,EAAnC,EAAuC;AACrC,UAAIrB,MAAM,CAACJ,UAAP,CAAkBsB,KAAK,CAACG,CAAD,CAAL,CAASb,IAA3B,MAAqCR,MAAM,CAACV,YAAP,EAAzC,EAAgE;ADGtE,ACFQY,QAAAA,CDECN,SCFS,CDElB,ACFmBsB,CDECrB,GAApB,CCFwB,CDEC,ACFAwB,CAAD,CAAN,CAAV;ADGN,ACFK,SDEE,CAAC,KAAKxB,GAAN,EAAWC,OAAX,CAAmB,sEAAnB,EAA2F,IAA3F,IAAmG,GAA1G;AACD,ACFI;;ADILC,ACFIgB,IAAAA,GDEG,CAACzB,MCFM,GAAG,GDEjB,CCFI,EDEmBD,kBAAvB;AACAU,ACFG,GATsB,EASpB,EDEE,ACXkB,CDWjBH,ACXN,UDWF,GAAqBA,UAArB;ACDC;;AAED2B,MAAM,CAACxB,OAAP,GAAiBiB,SAAjB","file":"index2.html","sourceRoot":"../src","sourcesContent":["var bundleURL = null;\nfunction getBundleURLCached() {\n if (!bundleURL) {\n bundleURL = getBundleURL();\n }\n\n return bundleURL;\n}\n\nfunction getBundleURL() {\n // Attempt to find the URL of the current script and use that as the base URL\n try {\n throw new Error;\n } catch (err) {\n var matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\\/\\/[^)\\n]+/g);\n if (matches) {\n return getBaseURL(matches[0]);\n }\n }\n\n return '/';\n}\n\nfunction getBaseURL(url) {\n return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\\/\\/.+)\\/[^/]+$/, '$1') + '/';\n}\n\nexports.getBundleURL = getBundleURLCached;\nexports.getBaseURL = getBaseURL;\n","var bundle = require('./bundle-url');\n\nfunction updateLink(link) {\n var newLink = link.cloneNode();\n newLink.onload = function () {\n link.remove();\n };\n newLink.href = link.href.split('?')[0] + '?' + Date.now();\n link.parentNode.insertBefore(newLink, link.nextSibling);\n}\n\nvar cssTimeout = null;\nfunction reloadCSS() {\n if (cssTimeout) {\n return;\n }\n\n cssTimeout = setTimeout(function () {\n var links = document.querySelectorAll('link[rel=\"stylesheet\"]');\n for (var i = 0; i < links.length; i++) {\n if (bundle.getBaseURL(links[i].href) === bundle.getBundleURL()) {\n updateLink(links[i]);\n }\n }\n\n cssTimeout = null;\n }, 50);\n}\n\nmodule.exports = reloadCSS;\n"]} -------------------------------------------------------------------------------- /dist/index2.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requires ] 3 | // 4 | // map of requires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the require for previous bundles 8 | parcelRequire = (function (modules, cache, entry, globalName) { 9 | // Save the require from previous bundle to this closure if any 10 | var previousRequire = typeof parcelRequire === 'function' && parcelRequire; 11 | var nodeRequire = typeof require === 'function' && require; 12 | 13 | function newRequire(name, jumped) { 14 | if (!cache[name]) { 15 | if (!modules[name]) { 16 | // if we cannot find the module within our internal map or 17 | // cache jump to the current global require ie. the last bundle 18 | // that was added to the page. 19 | var currentRequire = typeof parcelRequire === 'function' && parcelRequire; 20 | if (!jumped && currentRequire) { 21 | return currentRequire(name, true); 22 | } 23 | 24 | // If there are other bundles on this page the require from the 25 | // previous one is saved to 'previousRequire'. Repeat this as 26 | // many times as there are bundles until the module is found or 27 | // we exhaust the require chain. 28 | if (previousRequire) { 29 | return previousRequire(name, true); 30 | } 31 | 32 | // Try the node require function if it exists. 33 | if (nodeRequire && typeof name === 'string') { 34 | return nodeRequire(name); 35 | } 36 | 37 | var err = new Error('Cannot find module \'' + name + '\''); 38 | err.code = 'MODULE_NOT_FOUND'; 39 | throw err; 40 | } 41 | 42 | localRequire.resolve = resolve; 43 | localRequire.cache = {}; 44 | 45 | var module = cache[name] = new newRequire.Module(name); 46 | 47 | modules[name][0].call(module.exports, localRequire, module, module.exports, this); 48 | } 49 | 50 | return cache[name].exports; 51 | 52 | function localRequire(x){ 53 | return newRequire(localRequire.resolve(x)); 54 | } 55 | 56 | function resolve(x){ 57 | return modules[name][1][x] || x; 58 | } 59 | } 60 | 61 | function Module(moduleName) { 62 | this.id = moduleName; 63 | this.bundle = newRequire; 64 | this.exports = {}; 65 | } 66 | 67 | newRequire.isParcelRequire = true; 68 | newRequire.Module = Module; 69 | newRequire.modules = modules; 70 | newRequire.cache = cache; 71 | newRequire.parent = previousRequire; 72 | newRequire.register = function (id, exports) { 73 | modules[id] = [function (require, module) { 74 | module.exports = exports; 75 | }, {}]; 76 | }; 77 | 78 | var error; 79 | for (var i = 0; i < entry.length; i++) { 80 | try { 81 | newRequire(entry[i]); 82 | } catch (e) { 83 | // Save first error but execute all entries 84 | if (!error) { 85 | error = e; 86 | } 87 | } 88 | } 89 | 90 | if (entry.length) { 91 | // Expose entry point to Node, AMD or browser globals 92 | // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js 93 | var mainExports = newRequire(entry[entry.length - 1]); 94 | 95 | // CommonJS 96 | if (typeof exports === "object" && typeof module !== "undefined") { 97 | module.exports = mainExports; 98 | 99 | // RequireJS 100 | } else if (typeof define === "function" && define.amd) { 101 | define(function () { 102 | return mainExports; 103 | }); 104 | 105 | // 22 | 23 | 24 |
25 |
26 |

Animated Custom Cursor Effect

27 | 31 |

C

32 |
33 | 1 34 | 2 35 | 3 36 | 4 37 |
38 |
39 |
40 |

Hi, my name is Eddie and I am a pragmatic UI designer who has worked with dozens of businesses and some non-profit organizations. If you'd like me to help you with your next UI project, let's talk!

41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /dist/index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 28 | 29 | 30 |
31 |
32 |

Animated Custom Cursor Effect

33 | 37 |

C

38 |
39 | 1 40 | 2 41 | 3 42 | 4 43 |
44 |
45 |
46 | 52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /dist/messapia-bold-webfont.3267b3d0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/dist/messapia-bold-webfont.3267b3d0.woff2 -------------------------------------------------------------------------------- /dist/messapia-bold-webfont.6ddf630f.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/dist/messapia-bold-webfont.6ddf630f.woff -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AnimatedCustomCursor", 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-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/AnimatedCustomCursor.git" 15 | }, 16 | "keywords": [], 17 | "author": "Codrops", 18 | "license": "MIT", 19 | "homepage": "https://tympanus.net/Development/AnimatedCustomCursor/", 20 | "bugs": { 21 | "url": "https://github.com/codrops/AnimatedCustomCursor/issues" 22 | }, 23 | "dependencies": { 24 | "gsap": "^3.2.4", 25 | "parcel-bundler": "^1.12.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/.DS_Store -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | /dist 4 | package-lock.json -------------------------------------------------------------------------------- /src/css/demo1.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #c3654e; 14 | --color-bg: #f2e9e0; 15 | --color-link: #000; 16 | --color-link-hover: #040203; 17 | --color-content: #040203; 18 | --font-alt: freight-display-pro, serif; 19 | color: var(--color-text); 20 | background-color: var(--color-bg); 21 | font-family: soleil, sans-serif; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | /* Cursor styles */ 25 | --cursor-stroke: #c3654e; 26 | --cursor-fill: none; 27 | --cursor-stroke-width: 1px; 28 | } 29 | 30 | a { 31 | text-decoration: none; 32 | color: var(--color-link); 33 | outline: none; 34 | cursor: pointer; 35 | } 36 | 37 | a:hover, 38 | a:focus { 39 | color: var(--color-link-hover); 40 | outline: none; 41 | } 42 | 43 | .frame { 44 | padding: 3rem 5vw; 45 | text-align: center; 46 | position: relative; 47 | z-index: 1000; 48 | } 49 | 50 | .frame__title { 51 | font-size: 1rem; 52 | margin: 0 0 1rem; 53 | font-weight: inherit; 54 | } 55 | 56 | .frame__links { 57 | display: block; 58 | } 59 | 60 | .frame__links > *:not(:last-child), 61 | .frame__demos > *:not(:last-child) { 62 | margin-right: 1rem; 63 | } 64 | 65 | .frame__demos { 66 | margin: 1rem 0; 67 | } 68 | 69 | .frame__demos span { 70 | font-weight: bold; 71 | color: var(--color-link); 72 | } 73 | 74 | .frame__initials { 75 | font-family: var(--font-alt); 76 | font-size: 1.5rem; 77 | pointer-events: none; 78 | line-height: 18px; 79 | } 80 | 81 | .content { 82 | display: flex; 83 | position: relative; 84 | flex-direction: column; 85 | width: 100vw; 86 | height: calc(100vh - 13rem); 87 | position: relative; 88 | justify-content: flex-start; 89 | align-items: center; 90 | } 91 | 92 | .content p { 93 | max-width: 80vw; 94 | font-size: 4vw; 95 | text-transform: uppercase; 96 | color: var(--color-content); 97 | font-family: var(--font-alt); 98 | font-weight: 300; 99 | line-height: 1.4; 100 | } 101 | 102 | .content p a { 103 | font-weight: 600; 104 | font-style: italic; 105 | position: relative; 106 | } 107 | 108 | .content p a::after { 109 | content: ''; 110 | position: absolute; 111 | width: 100%; 112 | height: 1px; 113 | background: currentColor; 114 | bottom: 0.5vw; 115 | left: 0; 116 | opacity: 0; 117 | transform-origin: 0% 50%; 118 | transform: scaleX(0); 119 | transition: all 0.25s ease-out; 120 | } 121 | 122 | .content p a:hover::after, 123 | .content p a:focus::after { 124 | opacity: 1; 125 | transform: scaleX(1); 126 | } 127 | 128 | .menu { 129 | display: flex; 130 | overflow: hidden; 131 | position: relative; 132 | width: 100vw; 133 | justify-content: center; 134 | } 135 | 136 | .menu__item { 137 | font-size: 6.5vw; 138 | margin: 0 2vw; 139 | cursor: default; 140 | font-family: var(--font-alt); 141 | color: var(--color-menu-link); 142 | cursor: pointer; 143 | } 144 | 145 | .menu__item:hover, 146 | .menu__item:focus { 147 | color: var(--color-menu-link-hover); 148 | } 149 | 150 | .cursor { 151 | display: none; 152 | } 153 | 154 | @media screen and (min-width: 53em) { 155 | .frame { 156 | position: fixed; 157 | text-align: left; 158 | z-index: 10000; 159 | top: 0; 160 | left: 0; 161 | display: grid; 162 | align-content: space-between; 163 | width: 100%; 164 | max-width: none; 165 | height: 100vh; 166 | padding: 2.5rem 3rem; 167 | pointer-events: none; 168 | grid-template-columns: 40% 20% 40%; 169 | grid-template-rows: auto auto auto; 170 | grid-template-areas: 'title initials links' 171 | '... ... ...' 172 | '... demos ...'; 173 | } 174 | .frame__title { 175 | grid-area: title; 176 | margin: 0; 177 | } 178 | .frame__initials { 179 | grid-area: initials; 180 | margin: 0; 181 | justify-self: center; 182 | align-self: center; 183 | } 184 | .frame__demos { 185 | margin: 0; 186 | grid-area: demos; 187 | justify-self: center; 188 | } 189 | .frame__links { 190 | grid-area: links; 191 | padding: 0; 192 | justify-self: end; 193 | } 194 | .frame__info { 195 | grid-area: info; 196 | margin: 0; 197 | justify-self: end; 198 | } 199 | .frame a { 200 | pointer-events: auto; 201 | } 202 | .content { 203 | height: 100vh; 204 | justify-content: center; 205 | } 206 | } 207 | 208 | @media (any-pointer: fine) { 209 | .cursor { 210 | position: fixed; 211 | top: 0; 212 | left: 0; 213 | display: block; 214 | pointer-events: none; 215 | opacity: 0; 216 | } 217 | .cursor__inner { 218 | fill: var(--cursor-fill); 219 | stroke: var(--cursor-stroke); 220 | stroke-width: var(--cursor-stroke-width); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/css/demo2.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-link: #000; 14 | --color-link-hover: #040203; 15 | --color-content: #040203; 16 | --color-text: #040100; 17 | --color-bg: #8e806b; 18 | --font-alt: messapiabold, sans-serif; 19 | --color-menu-link: #e4d6b6; 20 | --color-menu-link-hover: #382d11; 21 | background-image: url(../img/1.jpg); 22 | background-repeat: no-repeat; 23 | background-position: 50% 50%; 24 | background-size: auto 60vh; 25 | color: var(--color-text); 26 | background-color: var(--color-bg); 27 | font-family: soleil, sans-serif; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-osx-font-smoothing: grayscale; 30 | /* Cursor styles */ 31 | --cursor-fill: none; 32 | --cursor-stroke: #60381e; 33 | --cursor-stroke-width: 1px; 34 | } 35 | 36 | a { 37 | text-decoration: none; 38 | color: var(--color-link); 39 | outline: none; 40 | cursor: pointer; 41 | } 42 | 43 | a:hover, 44 | a:focus { 45 | color: var(--color-link-hover); 46 | outline: none; 47 | } 48 | 49 | .frame { 50 | padding: 3rem 5vw; 51 | text-align: center; 52 | position: relative; 53 | z-index: 1000; 54 | } 55 | 56 | .frame__title { 57 | font-size: 1rem; 58 | margin: 0 0 1rem; 59 | font-weight: inherit; 60 | } 61 | 62 | .frame__links { 63 | display: block; 64 | } 65 | 66 | .frame__links > *:not(:last-child), 67 | .frame__demos > *:not(:last-child) { 68 | margin-right: 1rem; 69 | } 70 | 71 | .frame__demos { 72 | margin: 1rem 0; 73 | } 74 | 75 | .frame__demos span { 76 | font-weight: bold; 77 | color: var(--color-link); 78 | } 79 | 80 | .frame__initials { 81 | font-family: var(--font-alt); 82 | font-size: 1.5rem; 83 | pointer-events: none; 84 | line-height: 18px; 85 | } 86 | 87 | .content { 88 | display: flex; 89 | position: relative; 90 | flex-direction: column; 91 | width: 100vw; 92 | height: calc(100vh - 13rem); 93 | position: relative; 94 | justify-content: flex-start; 95 | align-items: center; 96 | } 97 | 98 | .content p { 99 | max-width: 80vw; 100 | font-size: 4vw; 101 | text-transform: uppercase; 102 | color: var(--color-content); 103 | font-family: var(--font-alt); 104 | font-weight: 300; 105 | line-height: 1.4; 106 | } 107 | 108 | .content p a { 109 | font-weight: 600; 110 | font-style: italic; 111 | position: relative; 112 | } 113 | 114 | .content p a::after { 115 | content: ''; 116 | position: absolute; 117 | width: 100%; 118 | height: 1px; 119 | background: currentColor; 120 | bottom: 0.5vw; 121 | left: 0; 122 | opacity: 0; 123 | transform-origin: 0% 50%; 124 | transform: scaleX(0); 125 | transition: all 0.25s ease-out; 126 | } 127 | 128 | .content p a:hover::after, 129 | .content p a:focus::after { 130 | opacity: 1; 131 | transform: scaleX(1); 132 | } 133 | 134 | .menu { 135 | display: flex; 136 | overflow: hidden; 137 | position: relative; 138 | width: 100vw; 139 | justify-content: center; 140 | } 141 | 142 | .menu__item { 143 | font-size: 6.5vw; 144 | margin: 0 2vw; 145 | cursor: default; 146 | font-family: var(--font-alt); 147 | color: var(--color-menu-link); 148 | cursor: pointer; 149 | } 150 | 151 | .menu__item:hover, 152 | .menu__item:focus { 153 | color: var(--color-menu-link-hover); 154 | } 155 | 156 | .cursor { 157 | display: none; 158 | } 159 | 160 | @media screen and (min-width: 53em) { 161 | .frame { 162 | position: fixed; 163 | text-align: left; 164 | z-index: 10000; 165 | top: 0; 166 | left: 0; 167 | display: grid; 168 | align-content: space-between; 169 | width: 100%; 170 | max-width: none; 171 | height: 100vh; 172 | padding: 2.5rem 3rem; 173 | pointer-events: none; 174 | grid-template-columns: 40% 20% 40%; 175 | grid-template-rows: auto auto auto; 176 | grid-template-areas: 'title initials links' 177 | '... ... ...' 178 | '... demos ...'; 179 | } 180 | .frame__title { 181 | grid-area: title; 182 | margin: 0; 183 | } 184 | .frame__initials { 185 | grid-area: initials; 186 | margin: 0; 187 | justify-self: center; 188 | align-self: center; 189 | } 190 | .frame__demos { 191 | margin: 0; 192 | grid-area: demos; 193 | justify-self: center; 194 | } 195 | .frame__links { 196 | grid-area: links; 197 | padding: 0; 198 | justify-self: end; 199 | } 200 | .frame__info { 201 | grid-area: info; 202 | margin: 0; 203 | justify-self: end; 204 | } 205 | .frame a { 206 | pointer-events: auto; 207 | } 208 | .content { 209 | height: 100vh; 210 | justify-content: center; 211 | } 212 | } 213 | 214 | @media (any-pointer: fine) { 215 | .cursor { 216 | position: fixed; 217 | top: 0; 218 | left: 0; 219 | display: block; 220 | pointer-events: none; 221 | opacity: 0; 222 | } 223 | .cursor__inner { 224 | fill: var(--cursor-fill); 225 | stroke: var(--cursor-stroke); 226 | stroke-width: var(--cursor-stroke-width); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/css/demo3.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #ffffff; 14 | --color-bg: #0e0e0e; 15 | --color-link: #d20d4c; 16 | --color-link-hover: #ffffff; 17 | --color-content: #4e4e4e; 18 | --font-alt: freight-display-pro, serif; 19 | color: var(--color-text); 20 | background-color: var(--color-bg); 21 | font-family: soleil, sans-serif; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | /* Cursor styles */ 25 | --cursor-stroke: #ffffff; 26 | --cursor-fill: none; 27 | --cursor-stroke-width: 1px; 28 | } 29 | 30 | a { 31 | text-decoration: none; 32 | color: var(--color-link); 33 | outline: none; 34 | cursor: pointer; 35 | } 36 | 37 | a:hover, 38 | a:focus { 39 | color: var(--color-link-hover); 40 | outline: none; 41 | } 42 | 43 | .message { 44 | background: var(--color-text); 45 | color: var(--color-bg); 46 | padding: 1rem; 47 | text-align: center; 48 | } 49 | 50 | .frame { 51 | padding: 3rem 5vw; 52 | text-align: center; 53 | position: relative; 54 | z-index: 1000; 55 | } 56 | 57 | .frame__title { 58 | font-size: 1rem; 59 | margin: 0 0 1rem; 60 | font-weight: inherit; 61 | } 62 | 63 | .frame__links { 64 | display: block; 65 | } 66 | 67 | .frame__links > *:not(:last-child), 68 | .frame__demos > *:not(:last-child) { 69 | margin-right: 1rem; 70 | } 71 | 72 | .frame__demos { 73 | margin: 1rem 0; 74 | } 75 | 76 | .frame__demos span { 77 | font-weight: bold; 78 | color: var(--color-link); 79 | } 80 | 81 | .frame__initials { 82 | font-family: var(--font-alt); 83 | font-size: 1.5rem; 84 | pointer-events: none; 85 | line-height: 18px; 86 | } 87 | 88 | .content { 89 | display: flex; 90 | position: relative; 91 | flex-direction: column; 92 | width: 100vw; 93 | height: calc(100vh - 13rem); 94 | position: relative; 95 | justify-content: flex-start; 96 | align-items: center; 97 | } 98 | 99 | .content p { 100 | max-width: 80vw; 101 | font-size: 4.5vw; 102 | text-transform: none; 103 | color: var(--color-content); 104 | font-family: var(--font-alt); 105 | font-weight: 300; 106 | line-height: 1.4; 107 | } 108 | 109 | .content p a { 110 | font-style: italic; 111 | position: relative; 112 | } 113 | 114 | .content p a::after { 115 | content: ''; 116 | position: absolute; 117 | width: 100%; 118 | height: 1px; 119 | background: currentColor; 120 | bottom: 0.5vw; 121 | left: 0; 122 | opacity: 0; 123 | transform-origin: 0% 50%; 124 | transform: scaleX(0); 125 | transition: all 0.25s ease-out; 126 | } 127 | 128 | .content p a:hover::after, 129 | .content p a:focus::after { 130 | opacity: 1; 131 | transform: scaleX(1); 132 | } 133 | 134 | .menu { 135 | display: flex; 136 | overflow: hidden; 137 | position: relative; 138 | width: 100vw; 139 | justify-content: center; 140 | } 141 | 142 | .menu__item { 143 | font-size: 6.5vw; 144 | margin: 0 2vw; 145 | cursor: default; 146 | font-family: var(--font-alt); 147 | color: var(--color-menu-link); 148 | cursor: pointer; 149 | } 150 | 151 | .menu__item:hover, 152 | .menu__item:focus { 153 | color: var(--color-menu-link-hover); 154 | } 155 | 156 | .cursor { 157 | display: none; 158 | } 159 | 160 | @media screen and (min-width: 53em) { 161 | .frame { 162 | position: fixed; 163 | text-align: left; 164 | z-index: 10000; 165 | top: 0; 166 | left: 0; 167 | display: grid; 168 | align-content: space-between; 169 | width: 100%; 170 | max-width: none; 171 | height: 100vh; 172 | padding: 2.5rem 3rem; 173 | pointer-events: none; 174 | grid-template-columns: 40% 20% 40%; 175 | grid-template-rows: auto auto auto; 176 | grid-template-areas: 'title initials links' 177 | '... ... ...' 178 | '... demos ...'; 179 | } 180 | .frame__title { 181 | grid-area: title; 182 | margin: 0; 183 | } 184 | .frame__initials { 185 | grid-area: initials; 186 | margin: 0; 187 | justify-self: center; 188 | align-self: center; 189 | } 190 | .frame__demos { 191 | margin: 0; 192 | grid-area: demos; 193 | justify-self: center; 194 | } 195 | .frame__links { 196 | grid-area: links; 197 | padding: 0; 198 | justify-self: end; 199 | } 200 | .frame__info { 201 | grid-area: info; 202 | margin: 0; 203 | justify-self: end; 204 | } 205 | .frame a { 206 | pointer-events: auto; 207 | } 208 | .content { 209 | height: 100vh; 210 | justify-content: center; 211 | } 212 | } 213 | 214 | @media (any-pointer: fine) { 215 | .cursor { 216 | position: fixed; 217 | top: 0; 218 | left: 0; 219 | display: block; 220 | pointer-events: none; 221 | opacity: 0; 222 | } 223 | .cursor__inner { 224 | fill: var(--cursor-fill); 225 | stroke: var(--cursor-stroke); 226 | stroke-width: var(--cursor-stroke-width); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/css/demo4.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #d03428; 14 | --color-bg: #000; 15 | --font-alt: messapiabold, sans-serif; 16 | --color-link: #e4d6b6; 17 | --color-link-hover: #d03428; 18 | --color-menu-link: #e4d6b6; 19 | --color-menu-link-hover: #d03428; 20 | --color-content: #040203; 21 | color: var(--color-text); 22 | background-color: var(--color-bg); 23 | font-family: soleil, sans-serif; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | background-image: url(../img/2.jpg); 27 | background-repeat: no-repeat; 28 | background-position: 50% 50%; 29 | background-size: auto 60vh; 30 | /* Cursor styles */ 31 | --cursor-stroke: #e4d6b6; 32 | --cursor-fill: none; 33 | --cursor-stroke-width: 1px; 34 | } 35 | 36 | a { 37 | text-decoration: none; 38 | color: var(--color-link); 39 | outline: none; 40 | cursor: pointer; 41 | } 42 | 43 | a:hover, 44 | a:focus { 45 | color: var(--color-link-hover); 46 | outline: none; 47 | } 48 | 49 | .message { 50 | background: var(--color-text); 51 | color: var(--color-bg); 52 | padding: 1rem; 53 | text-align: center; 54 | } 55 | 56 | .frame { 57 | padding: 3rem 5vw; 58 | text-align: center; 59 | position: relative; 60 | z-index: 1000; 61 | } 62 | 63 | .frame__title { 64 | font-size: 1rem; 65 | margin: 0 0 1rem; 66 | font-weight: inherit; 67 | } 68 | 69 | .frame__links { 70 | display: block; 71 | } 72 | 73 | .frame__links > *:not(:last-child), 74 | .frame__demos > *:not(:last-child) { 75 | margin-right: 1rem; 76 | } 77 | 78 | .frame__demos { 79 | margin: 1rem 0; 80 | } 81 | 82 | .frame__demos span { 83 | font-weight: bold; 84 | color: var(--color-link); 85 | } 86 | 87 | .frame__initials { 88 | font-family: var(--font-alt); 89 | font-size: 1.5rem; 90 | pointer-events: none; 91 | line-height: 18px; 92 | } 93 | 94 | .content { 95 | display: flex; 96 | position: relative; 97 | flex-direction: column; 98 | width: 100vw; 99 | height: calc(100vh - 13rem); 100 | position: relative; 101 | justify-content: flex-start; 102 | align-items: center; 103 | } 104 | 105 | .content p { 106 | max-width: 80vw; 107 | font-size: 4vw; 108 | text-transform: uppercase; 109 | color: var(--color-content); 110 | font-family: var(--font-alt); 111 | font-weight: 300; 112 | line-height: 1.4; 113 | } 114 | 115 | .content p a { 116 | font-weight: 600; 117 | font-style: italic; 118 | position: relative; 119 | } 120 | 121 | .content p a::after { 122 | content: ''; 123 | position: absolute; 124 | width: 100%; 125 | height: 1px; 126 | background: currentColor; 127 | bottom: 0.5vw; 128 | left: 0; 129 | opacity: 0; 130 | transform-origin: 0% 50%; 131 | transform: scaleX(0); 132 | transition: all 0.25s ease-out; 133 | } 134 | 135 | .content p a:hover::after, 136 | .content p a:focus::after { 137 | opacity: 1; 138 | transform: scaleX(1); 139 | } 140 | 141 | .menu { 142 | display: flex; 143 | overflow: hidden; 144 | position: relative; 145 | width: 100vw; 146 | justify-content: center; 147 | } 148 | 149 | .menu__item { 150 | font-size: 6.5vw; 151 | margin: 0 2vw; 152 | cursor: default; 153 | font-family: var(--font-alt); 154 | color: var(--color-menu-link); 155 | cursor: pointer; 156 | } 157 | 158 | .menu__item:hover, 159 | .menu__item:focus { 160 | color: var(--color-menu-link-hover); 161 | } 162 | 163 | .cursor { 164 | display: none; 165 | } 166 | 167 | @media screen and (min-width: 53em) { 168 | .frame { 169 | position: fixed; 170 | text-align: left; 171 | z-index: 10000; 172 | top: 0; 173 | left: 0; 174 | display: grid; 175 | align-content: space-between; 176 | width: 100%; 177 | max-width: none; 178 | height: 100vh; 179 | padding: 2.5rem 3rem; 180 | pointer-events: none; 181 | grid-template-columns: 40% 20% 40%; 182 | grid-template-rows: auto auto auto; 183 | grid-template-areas: 'title initials links' 184 | '... ... ...' 185 | '... demos ...'; 186 | } 187 | .frame__title { 188 | grid-area: title; 189 | margin: 0; 190 | } 191 | .frame__initials { 192 | grid-area: initials; 193 | margin: 0; 194 | justify-self: center; 195 | align-self: center; 196 | } 197 | .frame__demos { 198 | margin: 0; 199 | grid-area: demos; 200 | justify-self: center; 201 | } 202 | .frame__links { 203 | grid-area: links; 204 | padding: 0; 205 | justify-self: end; 206 | } 207 | .frame__info { 208 | grid-area: info; 209 | margin: 0; 210 | justify-self: end; 211 | } 212 | .frame a { 213 | pointer-events: auto; 214 | } 215 | .content { 216 | height: 100vh; 217 | justify-content: center; 218 | } 219 | } 220 | 221 | @media (any-pointer: fine) { 222 | .cursor { 223 | position: fixed; 224 | top: 0; 225 | left: 0; 226 | display: block; 227 | pointer-events: none; 228 | opacity: 0; 229 | } 230 | .cursor__inner { 231 | fill: var(--cursor-fill); 232 | stroke: var(--cursor-stroke); 233 | stroke-width: var(--cursor-stroke-width); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/favicon.ico -------------------------------------------------------------------------------- /src/fonts/messapia-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/fonts/messapia-bold-webfont.woff -------------------------------------------------------------------------------- /src/fonts/messapia-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/fonts/messapia-bold-webfont.woff2 -------------------------------------------------------------------------------- /src/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/img/1.jpg -------------------------------------------------------------------------------- /src/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/AnimatedCustomCursor/27dd6c4da069829c5773f7dcbc87a5fac5bc7fda/src/img/2.jpg -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Animated Custom Cursor Effect

19 | 23 |

C

24 |
25 | 1 26 | 2 27 | 3 28 | 4 29 |
30 |
31 |
32 |

Hi, my name is Eddie and I am a pragmatic UI designer who has worked with dozens of businesses and some non-profit organizations. If you'd like me to help you with your next UI project, let's talk!

33 |
34 |
35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 |
25 |
26 |

Animated Custom Cursor Effect

27 | 31 |

C

32 |
33 | 1 34 | 2 35 | 3 36 | 4 37 |
38 |
39 |
40 | 45 |
46 |
47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Animated Custom Cursor Effect

19 | 23 |

C

24 |
25 | 1 26 | 2 27 | 3 28 | 4 29 |
30 |
31 |
32 |

Hi, my name is Eddie and I am a pragmatic UI designer who has worked with dozens of businesses and some non-profit organizations. If you'd like me to help you with your next UI project, let's talk!

33 |
34 |
35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/index4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Animated Custom Cursor Effect | Demo 4 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 |
25 |
26 |

Animated Custom Cursor Effect

27 | 31 |

C

32 |
33 | 1 34 | 2 35 | 3 36 | 4 37 |
38 |
39 |
40 | 46 |
47 |
48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/js/cursor.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { map, lerp, calcWinsize, getMousePos } from './utils'; 3 | import { EventEmitter } from 'events'; 4 | 5 | // Calculate the viewport size 6 | let winsize = calcWinsize(); 7 | window.addEventListener('resize', () => { 8 | winsize = calcWinsize(); 9 | }); 10 | 11 | // Track the mouse position 12 | let mouse = {x: 0, y: 0}; 13 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 14 | 15 | export default class Cursor extends EventEmitter { 16 | constructor(el) { 17 | super(); 18 | this.DOM = {el: el}; 19 | this.DOM.el.style.opacity = 0; 20 | this.DOM.circleInner = this.DOM.el.querySelector('.cursor__inner'); 21 | 22 | this.filterId = '#filter-1'; 23 | this.DOM.feTurbulence = document.querySelector(`${this.filterId} > feTurbulence`); 24 | 25 | this.primitiveValues = {turbulence: 0}; 26 | 27 | this.createTimeline(); 28 | 29 | this.bounds = this.DOM.el.getBoundingClientRect(); 30 | 31 | this.renderedStyles = { 32 | tx: {previous: 0, current: 0, amt: 0.2}, 33 | ty: {previous: 0, current: 0, amt: 0.2}, 34 | radius: {previous: 60, current: 60, amt: 0.2} 35 | }; 36 | 37 | this.listen(); 38 | 39 | this.onMouseMoveEv = () => { 40 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 41 | this.renderedStyles.ty.previous = this.renderedStyles.ty.current = mouse.y - this.bounds.height/2; 42 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 43 | requestAnimationFrame(() => this.render()); 44 | window.removeEventListener('mousemove', this.onMouseMoveEv); 45 | }; 46 | window.addEventListener('mousemove', this.onMouseMoveEv); 47 | } 48 | render() { 49 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 50 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 51 | 52 | for (const key in this.renderedStyles ) { 53 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 54 | } 55 | 56 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px)`; 57 | this.DOM.circleInner.setAttribute('r', this.renderedStyles['radius'].previous); 58 | 59 | requestAnimationFrame(() => this.render()); 60 | } 61 | createTimeline() { 62 | // init timeline 63 | this.tl = gsap.timeline({ 64 | paused: true, 65 | onStart: () => { 66 | this.DOM.circleInner.style.filter = `url(${this.filterId}`; 67 | }, 68 | onUpdate: () => { 69 | this.DOM.feTurbulence.setAttribute('baseFrequency', this.primitiveValues.turbulence); 70 | }, 71 | onComplete: () => { 72 | this.DOM.circleInner.style.filter = 'none'; 73 | } 74 | }) 75 | .to(this.primitiveValues, { 76 | duration: 0.4, 77 | startAt: {turbulence: 0.09}, 78 | turbulence: 0 79 | }); 80 | } 81 | enter() { 82 | this.renderedStyles['radius'].current = 100; 83 | this.tl.restart(); 84 | } 85 | leave() { 86 | this.renderedStyles['radius'].current = 60; 87 | this.tl.progress(1).kill(); 88 | } 89 | listen() { 90 | this.on('enter', () => this.enter()); 91 | this.on('leave', () => this.leave()); 92 | } 93 | } -------------------------------------------------------------------------------- /src/js/cursor2.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { map, lerp, calcWinsize, getMousePos } from './utils'; 3 | import { EventEmitter } from 'events'; 4 | 5 | // Calculate the viewport size 6 | let winsize = calcWinsize(); 7 | window.addEventListener('resize', () => { 8 | winsize = calcWinsize(); 9 | }); 10 | 11 | // Track the mouse position 12 | let mouse = {x: 0, y: 0}; 13 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 14 | 15 | export default class Cursor extends EventEmitter { 16 | constructor(el) { 17 | super(); 18 | this.DOM = {el: el}; 19 | this.DOM.el.style.opacity = 0; 20 | this.DOM.circleInner = this.DOM.el.querySelector('.cursor__inner'); 21 | 22 | this.filterId = '#filter-1'; 23 | this.DOM.feTurbulence = document.querySelector(`${this.filterId} > feTurbulence`); 24 | 25 | this.primitiveValues = {turbulence: 0}; 26 | 27 | this.createTimeline(); 28 | 29 | this.bounds = this.DOM.el.getBoundingClientRect(); 30 | 31 | this.renderedStyles = { 32 | tx: {previous: 0, current: 0, amt: 0.2}, 33 | ty: {previous: 0, current: 0, amt: 0.2}, 34 | radius: {previous: 60, current: 60, amt: 0.2}, 35 | stroke: {previous: 1, current: 1, amt: 0.2} 36 | }; 37 | 38 | this.listen(); 39 | 40 | this.onMouseMoveEv = () => { 41 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 42 | this.renderedStyles.ty.previous = this.renderedStyles.ty.current = mouse.y - this.bounds.height/2; 43 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 44 | requestAnimationFrame(() => this.render()); 45 | window.removeEventListener('mousemove', this.onMouseMoveEv); 46 | }; 47 | window.addEventListener('mousemove', this.onMouseMoveEv); 48 | } 49 | render() { 50 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 51 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 52 | 53 | for (const key in this.renderedStyles ) { 54 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 55 | } 56 | 57 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px)`; 58 | this.DOM.circleInner.setAttribute('r', this.renderedStyles['radius'].previous); 59 | this.DOM.circleInner.style.strokeWidth = `${this.renderedStyles['stroke'].previous}px`; 60 | 61 | requestAnimationFrame(() => this.render()); 62 | } 63 | createTimeline() { 64 | // init timeline 65 | this.tl = gsap.timeline({ 66 | paused: true, 67 | onStart: () => { 68 | this.DOM.circleInner.style.filter = `url(${this.filterId}`; 69 | }, 70 | onUpdate: () => { 71 | this.DOM.feTurbulence.setAttribute('baseFrequency', this.primitiveValues.turbulence); 72 | }, 73 | onComplete: () => { 74 | this.DOM.circleInner.style.filter = 'none'; 75 | } 76 | }) 77 | .to(this.primitiveValues, { 78 | duration: 0.4, 79 | ease: "rough({ template: none.out, strength: 2, points: 120, taper: 'none', randomize: true, clamp: false})", 80 | startAt: {turbulence: 0.07}, 81 | turbulence: 0 82 | }); 83 | } 84 | enter() { 85 | this.renderedStyles['radius'].current = 40; 86 | this.renderedStyles['stroke'].current = 3; 87 | this.tl.restart(); 88 | } 89 | leave() { 90 | this.renderedStyles['radius'].current = 60; 91 | this.renderedStyles['stroke'].current = 1; 92 | this.tl.progress(1).kill(); 93 | } 94 | listen() { 95 | this.on('enter', () => this.enter()); 96 | this.on('leave', () => this.leave()); 97 | } 98 | } -------------------------------------------------------------------------------- /src/js/cursor3.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { map, lerp, calcWinsize, getMousePos } from './utils'; 3 | import { EventEmitter } from 'events'; 4 | 5 | // Calculate the viewport size 6 | let winsize = calcWinsize(); 7 | window.addEventListener('resize', () => { 8 | winsize = calcWinsize(); 9 | }); 10 | 11 | // Track the mouse position 12 | let mouse = {x: 0, y: 0}; 13 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 14 | 15 | export default class Cursor extends EventEmitter { 16 | constructor(el) { 17 | super(); 18 | this.DOM = {el: el}; 19 | this.DOM.el.style.opacity = 0; 20 | this.DOM.circleInner = this.DOM.el.querySelector('.cursor__inner'); 21 | 22 | this.filterId = '#filter-1'; 23 | this.DOM.feDisplacementMap = document.querySelector(`${this.filterId} > feDisplacementMap`); 24 | 25 | this.primitiveValues = {scale: 0}; 26 | 27 | this.createTimeline(); 28 | 29 | this.bounds = this.DOM.el.getBoundingClientRect(); 30 | 31 | this.renderedStyles = { 32 | tx: {previous: 0, current: 0, amt: 0.14}, 33 | ty: {previous: 0, current: 0, amt: 0.14}, 34 | radius: {previous: 50, current: 50, amt: 0.14} 35 | }; 36 | 37 | this.listen(); 38 | 39 | this.onMouseMoveEv = () => { 40 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 41 | this.renderedStyles.ty.previous = this.renderedStyles.ty.current = mouse.y - this.bounds.height/2; 42 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 43 | requestAnimationFrame(() => this.render()); 44 | window.removeEventListener('mousemove', this.onMouseMoveEv); 45 | }; 46 | window.addEventListener('mousemove', this.onMouseMoveEv); 47 | } 48 | render() { 49 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 50 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 51 | 52 | for (const key in this.renderedStyles ) { 53 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 54 | } 55 | 56 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px)`; 57 | this.DOM.circleInner.setAttribute('r', this.renderedStyles['radius'].previous); 58 | 59 | requestAnimationFrame(() => this.render()); 60 | } 61 | createTimeline() { 62 | // init timeline 63 | this.tl = gsap.timeline({ 64 | paused: true, 65 | onStart: () => { 66 | this.DOM.circleInner.style.filter = `url(${this.filterId}`; 67 | }, 68 | onUpdate: () => { 69 | this.DOM.feDisplacementMap.scale.baseVal = this.primitiveValues.scale; 70 | }, 71 | onComplete: () => { 72 | this.DOM.circleInner.style.filter = 'none'; 73 | } 74 | }) 75 | .to(this.primitiveValues, { 76 | duration: 0.1, 77 | ease: 'Expo.easeOut', 78 | startAt: {scale: 0}, 79 | scale: 60 80 | }) 81 | .to(this.primitiveValues, { 82 | duration: 0.6, 83 | ease: 'Power3.easeOut', 84 | scale: 0 85 | }); 86 | } 87 | enter() { 88 | this.renderedStyles['radius'].current = 120; 89 | this.tl.restart(); 90 | } 91 | leave() { 92 | this.renderedStyles['radius'].current = 50; 93 | this.tl.progress(1).kill(); 94 | } 95 | listen() { 96 | this.on('enter', () => this.enter()); 97 | this.on('leave', () => this.leave()); 98 | } 99 | } -------------------------------------------------------------------------------- /src/js/cursor4.js: -------------------------------------------------------------------------------- 1 | import { gsap } from 'gsap'; 2 | import { map, lerp, calcWinsize, getMousePos } from './utils'; 3 | import { EventEmitter } from 'events'; 4 | 5 | // Calculate the viewport size 6 | let winsize = calcWinsize(); 7 | window.addEventListener('resize', () => { 8 | winsize = calcWinsize(); 9 | }); 10 | 11 | // Track the mouse position 12 | let mouse = {x: 0, y: 0}; 13 | window.addEventListener('mousemove', ev => mouse = getMousePos(ev)); 14 | 15 | export default class Cursor extends EventEmitter { 16 | constructor(el) { 17 | super(); 18 | this.DOM = {el: el}; 19 | this.DOM.el.style.opacity = 0; 20 | this.DOM.circleInner = this.DOM.el.querySelector('.cursor__inner'); 21 | 22 | this.filterId = '#filter-1'; 23 | this.DOM.feTurbulence = document.querySelector(`${this.filterId} > feTurbulence`); 24 | 25 | this.primitiveValues = {turbulence: 0}; 26 | 27 | this.createTimeline(); 28 | 29 | this.bounds = this.DOM.el.getBoundingClientRect(); 30 | 31 | this.renderedStyles = { 32 | tx: {previous: 0, current: 0, amt: 0.16}, 33 | ty: {previous: 0, current: 0, amt: 0.16}, 34 | radius: {previous: 60, current: 60, amt: 0.16} 35 | }; 36 | 37 | this.listen(); 38 | 39 | this.onMouseMoveEv = () => { 40 | this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x - this.bounds.width/2; 41 | this.renderedStyles.ty.previous = this.renderedStyles.ty.current = mouse.y - this.bounds.height/2; 42 | gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1}); 43 | requestAnimationFrame(() => this.render()); 44 | window.removeEventListener('mousemove', this.onMouseMoveEv); 45 | }; 46 | window.addEventListener('mousemove', this.onMouseMoveEv); 47 | } 48 | render() { 49 | this.renderedStyles['tx'].current = mouse.x - this.bounds.width/2; 50 | this.renderedStyles['ty'].current = mouse.y - this.bounds.height/2; 51 | 52 | for (const key in this.renderedStyles ) { 53 | this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt); 54 | } 55 | 56 | this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px)`; 57 | this.DOM.circleInner.setAttribute('r', this.renderedStyles['radius'].previous); 58 | 59 | requestAnimationFrame(() => this.render()); 60 | } 61 | createTimeline() { 62 | // init timeline 63 | this.tl = gsap.timeline({ 64 | paused: true, 65 | onStart: () => { 66 | this.DOM.circleInner.style.filter = `url(${this.filterId}`; 67 | }, 68 | onUpdate: () => { 69 | this.DOM.feTurbulence.setAttribute('baseFrequency', this.primitiveValues.turbulence); 70 | }, 71 | onComplete: () => { 72 | this.DOM.circleInner.style.filter = 'none'; 73 | } 74 | }) 75 | .to(this.primitiveValues, { 76 | duration: 0.4, 77 | ease: 'Expo.easeOut', 78 | startAt: {turbulence: 0}, 79 | turbulence: 0.5 80 | }); 81 | } 82 | enter() { 83 | this.renderedStyles['radius'].current = 80; 84 | this.tl.restart(); 85 | } 86 | leave() { 87 | this.renderedStyles['radius'].current = 60; 88 | this.tl.progress(1).kill(); 89 | } 90 | listen() { 91 | this.on('enter', () => this.enter()); 92 | this.on('leave', () => this.leave()); 93 | } 94 | } -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import Cursor from './cursor'; 2 | 3 | const cursor = new Cursor(document.querySelector('.cursor')); 4 | 5 | [...document.querySelectorAll('a')].forEach(el => { 6 | el.addEventListener('mouseenter', () => cursor.emit('enter')); 7 | el.addEventListener('mouseleave', () => cursor.emit('leave')); 8 | }); -------------------------------------------------------------------------------- /src/js/index2.js: -------------------------------------------------------------------------------- 1 | import Cursor from './cursor2'; 2 | 3 | const cursor = new Cursor(document.querySelector('.cursor')); 4 | 5 | [...document.querySelectorAll('a')].forEach(el => { 6 | el.addEventListener('mouseenter', () => cursor.emit('enter')); 7 | el.addEventListener('mouseleave', () => cursor.emit('leave')); 8 | }); -------------------------------------------------------------------------------- /src/js/index3.js: -------------------------------------------------------------------------------- 1 | import Cursor from './cursor3'; 2 | 3 | const cursor = new Cursor(document.querySelector('.cursor')); 4 | 5 | [...document.querySelectorAll('a')].forEach(el => { 6 | el.addEventListener('mouseenter', () => cursor.emit('enter')); 7 | el.addEventListener('mouseleave', () => cursor.emit('leave')); 8 | }); -------------------------------------------------------------------------------- /src/js/index4.js: -------------------------------------------------------------------------------- 1 | import Cursor from './cursor4'; 2 | 3 | const cursor = new Cursor(document.querySelector('.cursor')); 4 | 5 | [...document.querySelectorAll('a')].forEach(el => { 6 | el.addEventListener('mouseenter', () => cursor.emit('enter')); 7 | el.addEventListener('mouseleave', () => cursor.emit('leave')); 8 | }); -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | // Map number x from range [a, b] to [c, d] 2 | const map = (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c; 3 | 4 | // Linear interpolation 5 | const lerp = (a, b, n) => (1 - n) * a + n * b; 6 | 7 | const calcWinsize = () => { 8 | return {width: window.innerWidth, height: window.innerHeight}; 9 | }; 10 | 11 | // Gets the mouse position 12 | const getMousePos = e => { 13 | return { 14 | x : e.clientX, 15 | y : e.clientY 16 | }; 17 | }; 18 | 19 | export { map, lerp, calcWinsize, getMousePos }; 20 | --------------------------------------------------------------------------------