├── .gitignore ├── .jshintrc ├── demos ├── assets │ ├── bat.gif │ ├── demo.gif │ ├── ghost.png │ ├── ghost_body.gif │ ├── ghost_eyes.gif │ ├── ghost_body_13.gif │ ├── ghost_body_kilt.gif │ ├── ghost_eyes_white.gif │ ├── ghost_body_tartan.gif │ ├── mark-github-black-128.png │ ├── mark-github-white-128.png │ ├── ghost_eyes_small_green.gif │ ├── ghost_eyes_small_white.gif │ └── style.css └── index.html ├── src ├── index.js └── MouseFollower.js ├── webpack.config.js ├── package.json ├── LICENSE.md ├── README.md └── dist └── mousefollower.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "asi": true 4 | } 5 | -------------------------------------------------------------------------------- /demos/assets/bat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/bat.gif -------------------------------------------------------------------------------- /demos/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/demo.gif -------------------------------------------------------------------------------- /demos/assets/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import MouseFollower from './MouseFollower.js'; 2 | window.MouseFollower = MouseFollower; 3 | -------------------------------------------------------------------------------- /demos/assets/ghost_body.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_body.gif -------------------------------------------------------------------------------- /demos/assets/ghost_eyes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_eyes.gif -------------------------------------------------------------------------------- /demos/assets/ghost_body_13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_body_13.gif -------------------------------------------------------------------------------- /demos/assets/ghost_body_kilt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_body_kilt.gif -------------------------------------------------------------------------------- /demos/assets/ghost_eyes_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_eyes_white.gif -------------------------------------------------------------------------------- /demos/assets/ghost_body_tartan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_body_tartan.gif -------------------------------------------------------------------------------- /demos/assets/mark-github-black-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/mark-github-black-128.png -------------------------------------------------------------------------------- /demos/assets/mark-github-white-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/mark-github-white-128.png -------------------------------------------------------------------------------- /demos/assets/ghost_eyes_small_green.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_eyes_small_green.gif -------------------------------------------------------------------------------- /demos/assets/ghost_eyes_small_white.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtBIT/mouse-follower/HEAD/demos/assets/ghost_eyes_small_white.gif -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require('path'); 3 | var version = require("./package.json").version; 4 | 5 | module.exports = { 6 | entry: { 7 | "mousefollower": path.join(__dirname, "src", "index.js"), 8 | }, 9 | output: { 10 | path: path.join(__dirname, 'dist'), 11 | filename: '[name].js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | loader: 'babel-loader' 18 | }] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mouse-follower", 3 | "version": "1.0.0", 4 | "description": "Create an avatar that follows the mouse cursor on the page.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "serve": "http-server -p 9090 .", 9 | "demo": "open http://localhost:9090/demos/", 10 | "start": "npm run build && npm run demo && npm run serve -s" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ArtBIT/mouse-follower.git" 15 | }, 16 | "keywords": [ 17 | "mouse", 18 | "cursor", 19 | "follower", 20 | "toy" 21 | ], 22 | "author": "Djordje Ungar", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/ArtBIT/mouse-follower/issues" 26 | }, 27 | "homepage": "https://github.com/ArtBIT/mouse-follower#readme", 28 | "dependencies": { 29 | "raf": "^3.3.2" 30 | }, 31 | "devDependencies": { 32 | "babel-core": "^6.23.1", 33 | "babel-loader": "^6.3.1", 34 | "babel-preset-es2015": "^6.22.0", 35 | "http-server": "^0.9.0", 36 | "open": "6.0.0", 37 | "webpack": "^2.2.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2017` `Djordje Ungar` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub stars](https://img.shields.io/github/stars/ArtBIT/mouse-follower.svg)](https://github.com/ArtBIT/mouse-follower) [![GitHub license](https://img.shields.io/github/license/ArtBIT/mouse-follower.svg)](https://github.com/ArtBIT/mouse-follower) [![GitHub issues](https://img.shields.io/github/issues/ArtBIT/mouse-follower.svg)](https://github.com/ArtBIT/mouse-follower/issues) 2 | 3 | # Mouse Follower 4 | 5 | This is a small experiment that creates a little avatar that follows mouse cursor on the page. 6 | 7 | Try the live demo [here](https://artbit.github.io/mouse-follower/demos/). 8 | 9 | [![mouse-follower](demos/assets/demo.gif)](http://github.com/artbit/mouse-follower/) 10 | 11 | ## Usage 12 | ```js 13 | var follower = new MouseFollower(); 14 | follower.show(); 15 | ``` 16 | To customize the follower you can use the following options: 17 | ```js 18 | var follower = new MouseFollower({ 19 | backgroundImage:'/demos/assets/ghost_body_tartan.gif', 20 | followStrategy: 'basic', /* basic|wobble */ 21 | width: 50, 22 | height: 50, 23 | offsetX: 25, 24 | offsetY: 25, 25 | opacity: 0.8, 26 | spring: 8, /* springiness coeficient */ 27 | inertia: 30, /* how agile the follower is */ 28 | wobble: 50, /* radius in pixels to wobble around the cursor */ 29 | xflip: false, /* should the sprite flip horizontally */ 30 | yflip: false, /* should the sprite flip vertically */ 31 | eyes: { 32 | backgroundImage:'/demos/assets/ghost_eyes.gif', 33 | width:12, 34 | height:16, 35 | radius: 5, 36 | offsetX:19, 37 | offsetY:16, 38 | opacity: 1 39 | } 40 | /* eyes: false - to disable */ 41 | }); 42 | follower.show(); 43 | }); 44 | ``` 45 | 46 | ## Local Build 47 | ``` 48 | git clone https://github.com/ArtBIT/mouse-follower.git 49 | cd mouse-follower 50 | npm install 51 | npm start 52 | ``` 53 | 54 | ## License 55 | MIT 56 | -------------------------------------------------------------------------------- /demos/assets/style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | -webkit-box-sizing: border-box; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | min-height: 100vh; 10 | height: auto; 11 | width: 100vw; 12 | font-family: Helvetica, Arial, sans-serif; 13 | font-size: 15px; 14 | margin: 0; 15 | padding: 0; 16 | color: #ddd; 17 | background-color: #111; 18 | -webkit-font-smoothing: antialiased; 19 | -moz-osx-font-smoothing: grayscale; 20 | overflow: hidden; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | border-color: transparent; 26 | color: #ddd; 27 | outline: none; 28 | padding: 0 0 0.15em; 29 | } 30 | 31 | a.active, 32 | a:hover, 33 | a:focus { 34 | color: #000; 35 | border-bottom: 2px solid; 36 | } 37 | 38 | .hidden { 39 | position: absolute; 40 | overflow: hidden; 41 | width: 0; 42 | height: 0; 43 | pointer-events: none; 44 | } 45 | 46 | header { 47 | padding: 1em; 48 | position: absolute; 49 | width: 100%; 50 | } 51 | 52 | header .row { 53 | display: flex; 54 | flex-direction: row; 55 | flex-wrap: wrap; 56 | align-items: center; 57 | width: 100%; 58 | } 59 | 60 | header .title { 61 | font-size: 1.85em; 62 | font-weight: normal; 63 | margin: 0; 64 | padding: 0; 65 | } 66 | 67 | header .github-link { 68 | height: 1em; 69 | width: 1em; 70 | display: inline-block; 71 | background-image: url(mark-github-white-128.png); 72 | background-repeat: no-repeat; 73 | background-size: cover; 74 | text-decoration: none; 75 | border: none; 76 | position: relative; 77 | top: 5px; 78 | padding: 2px; 79 | } 80 | 81 | header .tagline { 82 | margin: 1em 0 0.5em; 83 | width: 100%; 84 | } 85 | 86 | header .description { 87 | margin: 0 0 1em 0; 88 | font-weight: bold; 89 | width: 100%; 90 | } 91 | 92 | .demos { 93 | margin: 0 0 0 auto; 94 | } 95 | 96 | .demo { 97 | display: inline-block; 98 | margin: 0 1em 0.5em 0; 99 | padding: 0 0 0.25em; 100 | } 101 | 102 | .not-clickable { 103 | pointer-events: none; 104 | cursor: normal; 105 | } 106 | 107 | @media screen and (max-width: 60em) { 108 | .header { 109 | flex-direction: column; 110 | align-items: flex-start; 111 | font-size: 0.85em; 112 | } 113 | .demos { 114 | width: 100%; 115 | margin: 1em 0 0; 116 | } 117 | } 118 | 119 | /* THEME */ 120 | 121 | /* COLOR */ 122 | body, 123 | .demos, 124 | a, a:visited { 125 | color: #DDD; 126 | } 127 | 128 | /* HOVER COLOR */ 129 | a.active, 130 | a:hover, 131 | a:focus { 132 | color: #fff; 133 | } 134 | 135 | /* BACKGROUND */ 136 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Mouse Follower Demo 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 |
22 |
23 |

24 | 25 | Mouse Follower Demo 26 |

27 | 28 |
29 |
30 |

A simple toy that follows the mouse on screen.

31 |
32 |
33 |
34 | 35 | 175 | 176 | -------------------------------------------------------------------------------- /src/MouseFollower.js: -------------------------------------------------------------------------------- 1 | import raf from 'raf'; 2 | 3 | const ZINDEX = 1000; 4 | let instancesCount = 0; 5 | 6 | // Store mouse coordinates to global objects which is accessible to all the instances of MouseFollower 7 | const mouse = {x: 0, y: 0}; 8 | document.addEventListener('mousemove', function(e) { 9 | mouse.x = e.pageX; 10 | mouse.y = e.pageY; 11 | }); 12 | 13 | // Helper method to style html elements 14 | let setElementAttributes = function(elem, attrs) { 15 | attrs = attrs || {}; 16 | for (const a in attrs) { 17 | if (attrs.hasOwnProperty(a)) { 18 | switch (a) { 19 | case 'data': 20 | for (const d in attrs[a]) { 21 | if (attrs[a].hasOwnProperty(d)) { 22 | elem.setAttribute('data-'+d, attrs[a][d]); 23 | } 24 | } 25 | break; 26 | case 'style': 27 | for (const s in attrs[a]) { 28 | if (attrs[a].hasOwnProperty(s)) { 29 | elem.style[s] = attrs[a][s]; 30 | } 31 | } 32 | break; 33 | case 'className': 34 | case 'innerHTML': 35 | elem[a] = attrs[a]; 36 | break; 37 | 38 | default: 39 | elem.setAttribute(a, attrs[a]); 40 | } 41 | } 42 | } 43 | } 44 | 45 | // Different follow strategies 46 | let followStrategies = { 47 | basic: function(follower) { 48 | var o = follower.options; 49 | var dx = mouse.x - follower.x; 50 | var dy = mouse.y - follower.y; 51 | 52 | follower.tx += dx / o.inertia; 53 | follower.ty += dy / o.inertia; 54 | 55 | follower.x += (follower.tx - follower.x) / o.spring; 56 | follower.y += (follower.ty - follower.y) / o.spring; 57 | }, 58 | wobble: function(follower) { 59 | var o = follower.options; 60 | 61 | follower.dx = follower.dx || 0; 62 | follower.dy = follower.dy || 0; 63 | 64 | var ox = (follower.tx - follower.x); 65 | var oy = (follower.ty - follower.y); 66 | var od = Math.sqrt(ox*ox + oy*oy) || 2; 67 | 68 | var dx = o.spring * (ox / od); 69 | var dy = o.spring * (oy / od); 70 | 71 | var ddx = (dx - follower.dx) / o.inertia; 72 | var ddy = (dy - follower.dy) / o.inertia; 73 | follower.dx += ddx; 74 | follower.dy += ddy; 75 | 76 | follower.x += follower.dx; 77 | follower.y += follower.dy; 78 | 79 | follower.tx = mouse.x + (Math.random() - 0.5) * o.wobble; 80 | follower.ty = mouse.y + (Math.random() - 0.5) * o.wobble; 81 | }, 82 | eyes: function(follower) { 83 | var e = follower.options.eyes; 84 | var dx = mouse.x - (follower.x);// + e.offsetX); 85 | var dy = mouse.y - (follower.y);// + e.offsetY) 86 | var d = dx*dx + dy*dy; 87 | var ex = (follower.options.width - e.width)>>1; 88 | var ey = (follower.options.height - e.height)>>1; 89 | if (d > e.radius) { 90 | var ang = Math.atan2(dy,dx); 91 | ex += e.radius * Math.cos(ang); 92 | ey += e.radius * Math.sin(ang); 93 | } 94 | follower.eyes.style.transform = `translate(${ex}px, ${ey}px)`; 95 | } 96 | }; 97 | 98 | export default class MouseFollower { 99 | constructor(options) { 100 | this.id = instancesCount++; 101 | // element x,y 102 | this.x = 0; 103 | this.y = 0; 104 | // target x,y 105 | this.tx = 0; 106 | this.ty = 0; 107 | this.node = document.createElement('div'); 108 | setElementAttributes(this.node, { 109 | id: 'follower-'+this.id, 110 | style: { 111 | position: 'absolute', 112 | pointerEvents: 'none', 113 | backgroundSize: 'contain', 114 | imageRendering: 'pixelated', 115 | backgroundRepeat: 'no-repeat' 116 | } 117 | }); 118 | this.eyes = document.createElement('div'); 119 | setElementAttributes(this.eyes, { 120 | style: { 121 | position: 'absolute', 122 | backgroundRepeat: 'no-repeat', 123 | imageRendering: 'pixelated', 124 | display: 'none' 125 | } 126 | }); 127 | this.node.appendChild(this.eyes); 128 | document.body.appendChild(this.node); 129 | this.setOptions(options); 130 | this.enabled = false; 131 | } 132 | 133 | tick() { 134 | if (this.enabled) { 135 | this.update(); 136 | raf(()=>this.tick()); 137 | } 138 | } 139 | 140 | update() { 141 | // Update position 142 | followStrategies[this.options.followStrategy || 'basic'](this); 143 | var transforms = []; 144 | transforms.push(`translate(${this.x-this.options.offsetX}px, ${this.y-this.options.offsetY}px)`); 145 | if (this.options.xflip && (this.x > mouse.x)) { 146 | transforms.push('scaleX(-1)'); 147 | } else { 148 | transforms.push('scaleX(1)'); 149 | } 150 | if (this.options.yflip && (this.y > mouse.y)) { 151 | transforms.push('scaleY(-1)'); 152 | } else { 153 | transforms.push('scaleY(1)'); 154 | } 155 | this.node.style.transform = transforms.join(' '); 156 | 157 | if (this.options.eyes) { 158 | followStrategies.eyes(this); 159 | } 160 | } 161 | 162 | setOptions(options) { 163 | options = options || {}; 164 | let defaultOptions = { 165 | backgroundImage:'assets/ghost_body.gif', 166 | followStrategy: 'basic', 167 | width: 50, 168 | height: 50, 169 | offsetX: 25, 170 | offsetY: 25, 171 | opacity: 0.8, 172 | spring: 8, 173 | inertia: 30, 174 | wobble: 50, 175 | xflip: false, 176 | yflip: false, 177 | eyes: { 178 | backgroundImage:'assets/ghost_eyes.gif', 179 | width:12, 180 | height:16, 181 | radius: 5, 182 | offsetX:19, 183 | offsetY:16, 184 | opacity: 1 185 | } 186 | }; 187 | this.options = {}; 188 | for (const o in defaultOptions) { 189 | if (o == 'eyes') { 190 | if (options[o] === false) { 191 | this.options[o] = false; 192 | } else { 193 | options[o] = options[o] || {}; 194 | this.options[o] = {}; 195 | for (const e in defaultOptions[o]) { 196 | this.options[o][e] = options[o].hasOwnProperty(e) ? options[o][e] : defaultOptions[o][e]; 197 | } 198 | } 199 | } else { 200 | this.options[o] = options.hasOwnProperty(o) ? options[o] : defaultOptions[o]; 201 | } 202 | } 203 | 204 | setElementAttributes(this.node, { 205 | style: { 206 | backgroundImage: 'url("'+this.options.backgroundImage+'")', 207 | width: this.options.width + 'px', 208 | height: this.options.height + 'px', 209 | opacity: this.options.opacity, 210 | zIndex: this.options.zindex || ZINDEX 211 | } 212 | }); 213 | if (this.options.eyes) { 214 | setElementAttributes(this.eyes, { 215 | style: { 216 | display: '', 217 | backgroundImage: 'url("'+this.options.eyes.backgroundImage+'")', 218 | width: this.options.eyes.width + 'px', 219 | height: this.options.eyes.height + 'px', 220 | opacity: this.options.eyes.opacity 221 | } 222 | }); 223 | } else { 224 | this.eyes.style.display = 'none'; 225 | } 226 | } 227 | 228 | start() { 229 | this.enabled = true; 230 | this.tick(); 231 | } 232 | 233 | stop() { 234 | this.enabled = false; 235 | } 236 | 237 | hide() { 238 | this.node.style.display = "none"; 239 | this.stop(); 240 | } 241 | 242 | show() { 243 | this.node.style.display = ""; 244 | this.start(); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /dist/mousefollower.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // identity function for calling harmony imports with the correct context 37 | /******/ __webpack_require__.i = function(value) { return value; }; 38 | /******/ 39 | /******/ // define getter function for harmony exports 40 | /******/ __webpack_require__.d = function(exports, name, getter) { 41 | /******/ if(!__webpack_require__.o(exports, name)) { 42 | /******/ Object.defineProperty(exports, name, { 43 | /******/ configurable: false, 44 | /******/ enumerable: true, 45 | /******/ get: getter 46 | /******/ }); 47 | /******/ } 48 | /******/ }; 49 | /******/ 50 | /******/ // getDefaultExport function for compatibility with non-harmony modules 51 | /******/ __webpack_require__.n = function(module) { 52 | /******/ var getter = module && module.__esModule ? 53 | /******/ function getDefault() { return module['default']; } : 54 | /******/ function getModuleExports() { return module; }; 55 | /******/ __webpack_require__.d(getter, 'a', getter); 56 | /******/ return getter; 57 | /******/ }; 58 | /******/ 59 | /******/ // Object.prototype.hasOwnProperty.call 60 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 61 | /******/ 62 | /******/ // __webpack_public_path__ 63 | /******/ __webpack_require__.p = ""; 64 | /******/ 65 | /******/ // Load entry module and return exports 66 | /******/ return __webpack_require__(__webpack_require__.s = 1); 67 | /******/ }) 68 | /************************************************************************/ 69 | /******/ ([ 70 | /* 0 */ 71 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 72 | 73 | "use strict"; 74 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_raf__ = __webpack_require__(4); 75 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_raf___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_raf__); 76 | 77 | 78 | const ZINDEX = 1000; 79 | let instancesCount = 0; 80 | 81 | // Store mouse coordinates to global objects which is accessible to all the instances of MouseFollower 82 | const mouse = { x: 0, y: 0 }; 83 | document.addEventListener('mousemove', function (e) { 84 | mouse.x = e.pageX; 85 | mouse.y = e.pageY; 86 | }); 87 | 88 | // Helper method to style html elements 89 | let setElementAttributes = function (elem, attrs) { 90 | attrs = attrs || {}; 91 | for (const a in attrs) { 92 | if (attrs.hasOwnProperty(a)) { 93 | switch (a) { 94 | case 'data': 95 | for (const d in attrs[a]) { 96 | if (attrs[a].hasOwnProperty(d)) { 97 | elem.setAttribute('data-' + d, attrs[a][d]); 98 | } 99 | } 100 | break; 101 | case 'style': 102 | for (const s in attrs[a]) { 103 | if (attrs[a].hasOwnProperty(s)) { 104 | elem.style[s] = attrs[a][s]; 105 | } 106 | } 107 | break; 108 | case 'className': 109 | case 'innerHTML': 110 | elem[a] = attrs[a]; 111 | break; 112 | 113 | default: 114 | elem.setAttribute(a, attrs[a]); 115 | } 116 | } 117 | } 118 | }; 119 | 120 | // Different follow strategies 121 | let followStrategies = { 122 | basic: function (follower) { 123 | var o = follower.options; 124 | var dx = mouse.x - follower.x; 125 | var dy = mouse.y - follower.y; 126 | 127 | follower.tx += dx / o.inertia; 128 | follower.ty += dy / o.inertia; 129 | 130 | follower.x += (follower.tx - follower.x) / o.spring; 131 | follower.y += (follower.ty - follower.y) / o.spring; 132 | }, 133 | wobble: function (follower) { 134 | var o = follower.options; 135 | 136 | follower.dx = follower.dx || 0; 137 | follower.dy = follower.dy || 0; 138 | 139 | var ox = follower.tx - follower.x; 140 | var oy = follower.ty - follower.y; 141 | var od = Math.sqrt(ox * ox + oy * oy) || 2; 142 | 143 | var dx = o.spring * (ox / od); 144 | var dy = o.spring * (oy / od); 145 | 146 | var ddx = (dx - follower.dx) / o.inertia; 147 | var ddy = (dy - follower.dy) / o.inertia; 148 | follower.dx += ddx; 149 | follower.dy += ddy; 150 | 151 | follower.x += follower.dx; 152 | follower.y += follower.dy; 153 | 154 | follower.tx = mouse.x + (Math.random() - 0.5) * o.wobble; 155 | follower.ty = mouse.y + (Math.random() - 0.5) * o.wobble; 156 | }, 157 | eyes: function (follower) { 158 | var e = follower.options.eyes; 159 | var dx = mouse.x - follower.x; // + e.offsetX); 160 | var dy = mouse.y - follower.y; // + e.offsetY) 161 | var d = dx * dx + dy * dy; 162 | var ex = follower.options.width - e.width >> 1; 163 | var ey = follower.options.height - e.height >> 1; 164 | if (d > e.radius) { 165 | var ang = Math.atan2(dy, dx); 166 | ex += e.radius * Math.cos(ang); 167 | ey += e.radius * Math.sin(ang); 168 | } 169 | follower.eyes.style.transform = `translate(${ex}px, ${ey}px)`; 170 | } 171 | }; 172 | 173 | class MouseFollower { 174 | constructor(options) { 175 | this.id = instancesCount++; 176 | // element x,y 177 | this.x = 0; 178 | this.y = 0; 179 | // target x,y 180 | this.tx = 0; 181 | this.ty = 0; 182 | this.node = document.createElement('div'); 183 | setElementAttributes(this.node, { 184 | id: 'follower-' + this.id, 185 | style: { 186 | position: 'absolute', 187 | pointerEvents: 'none', 188 | backgroundSize: 'contain', 189 | imageRendering: 'pixelated', 190 | backgroundRepeat: 'no-repeat' 191 | } 192 | }); 193 | this.eyes = document.createElement('div'); 194 | setElementAttributes(this.eyes, { 195 | style: { 196 | position: 'absolute', 197 | backgroundRepeat: 'no-repeat', 198 | imageRendering: 'pixelated', 199 | display: 'none' 200 | } 201 | }); 202 | this.node.appendChild(this.eyes); 203 | document.body.appendChild(this.node); 204 | this.setOptions(options); 205 | this.enabled = false; 206 | } 207 | 208 | tick() { 209 | if (this.enabled) { 210 | this.update(); 211 | __WEBPACK_IMPORTED_MODULE_0_raf___default()(() => this.tick()); 212 | } 213 | } 214 | 215 | update() { 216 | // Update position 217 | followStrategies[this.options.followStrategy || 'basic'](this); 218 | var transforms = []; 219 | transforms.push(`translate(${this.x - this.options.offsetX}px, ${this.y - this.options.offsetY}px)`); 220 | if (this.options.xflip && this.x > mouse.x) { 221 | transforms.push('scaleX(-1)'); 222 | } else { 223 | transforms.push('scaleX(1)'); 224 | } 225 | if (this.options.yflip && this.y > mouse.y) { 226 | transforms.push('scaleY(-1)'); 227 | } else { 228 | transforms.push('scaleY(1)'); 229 | } 230 | this.node.style.transform = transforms.join(' '); 231 | 232 | if (this.options.eyes) { 233 | followStrategies.eyes(this); 234 | } 235 | } 236 | 237 | setOptions(options) { 238 | options = options || {}; 239 | let defaultOptions = { 240 | backgroundImage: 'assets/ghost_body.gif', 241 | followStrategy: 'basic', 242 | width: 50, 243 | height: 50, 244 | offsetX: 25, 245 | offsetY: 25, 246 | opacity: 0.8, 247 | spring: 8, 248 | inertia: 30, 249 | wobble: 50, 250 | xflip: false, 251 | yflip: false, 252 | eyes: { 253 | backgroundImage: 'assets/ghost_eyes.gif', 254 | width: 12, 255 | height: 16, 256 | radius: 5, 257 | offsetX: 19, 258 | offsetY: 16, 259 | opacity: 1 260 | } 261 | }; 262 | this.options = {}; 263 | for (const o in defaultOptions) { 264 | if (o == 'eyes') { 265 | if (options[o] === false) { 266 | this.options[o] = false; 267 | } else { 268 | options[o] = options[o] || {}; 269 | this.options[o] = {}; 270 | for (const e in defaultOptions[o]) { 271 | this.options[o][e] = options[o].hasOwnProperty(e) ? options[o][e] : defaultOptions[o][e]; 272 | } 273 | } 274 | } else { 275 | this.options[o] = options.hasOwnProperty(o) ? options[o] : defaultOptions[o]; 276 | } 277 | } 278 | 279 | setElementAttributes(this.node, { 280 | style: { 281 | backgroundImage: 'url("' + this.options.backgroundImage + '")', 282 | width: this.options.width + 'px', 283 | height: this.options.height + 'px', 284 | opacity: this.options.opacity, 285 | zIndex: this.options.zindex || ZINDEX 286 | } 287 | }); 288 | if (this.options.eyes) { 289 | setElementAttributes(this.eyes, { 290 | style: { 291 | display: '', 292 | backgroundImage: 'url("' + this.options.eyes.backgroundImage + '")', 293 | width: this.options.eyes.width + 'px', 294 | height: this.options.eyes.height + 'px', 295 | opacity: this.options.eyes.opacity 296 | } 297 | }); 298 | } else { 299 | this.eyes.style.display = 'none'; 300 | } 301 | } 302 | 303 | start() { 304 | this.enabled = true; 305 | this.tick(); 306 | } 307 | 308 | stop() { 309 | this.enabled = false; 310 | } 311 | 312 | hide() { 313 | this.node.style.display = "none"; 314 | this.stop(); 315 | } 316 | 317 | show() { 318 | this.node.style.display = ""; 319 | this.start(); 320 | } 321 | } 322 | /* harmony export (immutable) */ __webpack_exports__["a"] = MouseFollower; 323 | 324 | 325 | /***/ }), 326 | /* 1 */ 327 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 328 | 329 | "use strict"; 330 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); 331 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__MouseFollower_js__ = __webpack_require__(0); 332 | 333 | window.MouseFollower = __WEBPACK_IMPORTED_MODULE_0__MouseFollower_js__["a" /* default */]; 334 | 335 | /***/ }), 336 | /* 2 */ 337 | /***/ (function(module, exports, __webpack_require__) { 338 | 339 | /* WEBPACK VAR INJECTION */(function(process) {// Generated by CoffeeScript 1.12.2 340 | (function() { 341 | var getNanoSeconds, hrtime, loadTime, moduleLoadTime, nodeLoadTime, upTime; 342 | 343 | if ((typeof performance !== "undefined" && performance !== null) && performance.now) { 344 | module.exports = function() { 345 | return performance.now(); 346 | }; 347 | } else if ((typeof process !== "undefined" && process !== null) && process.hrtime) { 348 | module.exports = function() { 349 | return (getNanoSeconds() - nodeLoadTime) / 1e6; 350 | }; 351 | hrtime = process.hrtime; 352 | getNanoSeconds = function() { 353 | var hr; 354 | hr = hrtime(); 355 | return hr[0] * 1e9 + hr[1]; 356 | }; 357 | moduleLoadTime = getNanoSeconds(); 358 | upTime = process.uptime() * 1e9; 359 | nodeLoadTime = moduleLoadTime - upTime; 360 | } else if (Date.now) { 361 | module.exports = function() { 362 | return Date.now() - loadTime; 363 | }; 364 | loadTime = Date.now(); 365 | } else { 366 | module.exports = function() { 367 | return new Date().getTime() - loadTime; 368 | }; 369 | loadTime = new Date().getTime(); 370 | } 371 | 372 | }).call(this); 373 | 374 | //# sourceMappingURL=performance-now.js.map 375 | 376 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3))) 377 | 378 | /***/ }), 379 | /* 3 */ 380 | /***/ (function(module, exports) { 381 | 382 | // shim for using process in browser 383 | var process = module.exports = {}; 384 | 385 | // cached from whatever global is present so that test runners that stub it 386 | // don't break things. But we need to wrap it in a try catch in case it is 387 | // wrapped in strict mode code which doesn't define any globals. It's inside a 388 | // function because try/catches deoptimize in certain engines. 389 | 390 | var cachedSetTimeout; 391 | var cachedClearTimeout; 392 | 393 | function defaultSetTimout() { 394 | throw new Error('setTimeout has not been defined'); 395 | } 396 | function defaultClearTimeout () { 397 | throw new Error('clearTimeout has not been defined'); 398 | } 399 | (function () { 400 | try { 401 | if (typeof setTimeout === 'function') { 402 | cachedSetTimeout = setTimeout; 403 | } else { 404 | cachedSetTimeout = defaultSetTimout; 405 | } 406 | } catch (e) { 407 | cachedSetTimeout = defaultSetTimout; 408 | } 409 | try { 410 | if (typeof clearTimeout === 'function') { 411 | cachedClearTimeout = clearTimeout; 412 | } else { 413 | cachedClearTimeout = defaultClearTimeout; 414 | } 415 | } catch (e) { 416 | cachedClearTimeout = defaultClearTimeout; 417 | } 418 | } ()) 419 | function runTimeout(fun) { 420 | if (cachedSetTimeout === setTimeout) { 421 | //normal enviroments in sane situations 422 | return setTimeout(fun, 0); 423 | } 424 | // if setTimeout wasn't available but was latter defined 425 | if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { 426 | cachedSetTimeout = setTimeout; 427 | return setTimeout(fun, 0); 428 | } 429 | try { 430 | // when when somebody has screwed with setTimeout but no I.E. maddness 431 | return cachedSetTimeout(fun, 0); 432 | } catch(e){ 433 | try { 434 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 435 | return cachedSetTimeout.call(null, fun, 0); 436 | } catch(e){ 437 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error 438 | return cachedSetTimeout.call(this, fun, 0); 439 | } 440 | } 441 | 442 | 443 | } 444 | function runClearTimeout(marker) { 445 | if (cachedClearTimeout === clearTimeout) { 446 | //normal enviroments in sane situations 447 | return clearTimeout(marker); 448 | } 449 | // if clearTimeout wasn't available but was latter defined 450 | if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { 451 | cachedClearTimeout = clearTimeout; 452 | return clearTimeout(marker); 453 | } 454 | try { 455 | // when when somebody has screwed with setTimeout but no I.E. maddness 456 | return cachedClearTimeout(marker); 457 | } catch (e){ 458 | try { 459 | // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally 460 | return cachedClearTimeout.call(null, marker); 461 | } catch (e){ 462 | // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. 463 | // Some versions of I.E. have different rules for clearTimeout vs setTimeout 464 | return cachedClearTimeout.call(this, marker); 465 | } 466 | } 467 | 468 | 469 | 470 | } 471 | var queue = []; 472 | var draining = false; 473 | var currentQueue; 474 | var queueIndex = -1; 475 | 476 | function cleanUpNextTick() { 477 | if (!draining || !currentQueue) { 478 | return; 479 | } 480 | draining = false; 481 | if (currentQueue.length) { 482 | queue = currentQueue.concat(queue); 483 | } else { 484 | queueIndex = -1; 485 | } 486 | if (queue.length) { 487 | drainQueue(); 488 | } 489 | } 490 | 491 | function drainQueue() { 492 | if (draining) { 493 | return; 494 | } 495 | var timeout = runTimeout(cleanUpNextTick); 496 | draining = true; 497 | 498 | var len = queue.length; 499 | while(len) { 500 | currentQueue = queue; 501 | queue = []; 502 | while (++queueIndex < len) { 503 | if (currentQueue) { 504 | currentQueue[queueIndex].run(); 505 | } 506 | } 507 | queueIndex = -1; 508 | len = queue.length; 509 | } 510 | currentQueue = null; 511 | draining = false; 512 | runClearTimeout(timeout); 513 | } 514 | 515 | process.nextTick = function (fun) { 516 | var args = new Array(arguments.length - 1); 517 | if (arguments.length > 1) { 518 | for (var i = 1; i < arguments.length; i++) { 519 | args[i - 1] = arguments[i]; 520 | } 521 | } 522 | queue.push(new Item(fun, args)); 523 | if (queue.length === 1 && !draining) { 524 | runTimeout(drainQueue); 525 | } 526 | }; 527 | 528 | // v8 likes predictible objects 529 | function Item(fun, array) { 530 | this.fun = fun; 531 | this.array = array; 532 | } 533 | Item.prototype.run = function () { 534 | this.fun.apply(null, this.array); 535 | }; 536 | process.title = 'browser'; 537 | process.browser = true; 538 | process.env = {}; 539 | process.argv = []; 540 | process.version = ''; // empty string to avoid regexp issues 541 | process.versions = {}; 542 | 543 | function noop() {} 544 | 545 | process.on = noop; 546 | process.addListener = noop; 547 | process.once = noop; 548 | process.off = noop; 549 | process.removeListener = noop; 550 | process.removeAllListeners = noop; 551 | process.emit = noop; 552 | process.prependListener = noop; 553 | process.prependOnceListener = noop; 554 | 555 | process.listeners = function (name) { return [] } 556 | 557 | process.binding = function (name) { 558 | throw new Error('process.binding is not supported'); 559 | }; 560 | 561 | process.cwd = function () { return '/' }; 562 | process.chdir = function (dir) { 563 | throw new Error('process.chdir is not supported'); 564 | }; 565 | process.umask = function() { return 0; }; 566 | 567 | 568 | /***/ }), 569 | /* 4 */ 570 | /***/ (function(module, exports, __webpack_require__) { 571 | 572 | /* WEBPACK VAR INJECTION */(function(global) {var now = __webpack_require__(2) 573 | , root = typeof window === 'undefined' ? global : window 574 | , vendors = ['moz', 'webkit'] 575 | , suffix = 'AnimationFrame' 576 | , raf = root['request' + suffix] 577 | , caf = root['cancel' + suffix] || root['cancelRequest' + suffix] 578 | 579 | for(var i = 0; !raf && i < vendors.length; i++) { 580 | raf = root[vendors[i] + 'Request' + suffix] 581 | caf = root[vendors[i] + 'Cancel' + suffix] 582 | || root[vendors[i] + 'CancelRequest' + suffix] 583 | } 584 | 585 | // Some versions of FF have rAF but not cAF 586 | if(!raf || !caf) { 587 | var last = 0 588 | , id = 0 589 | , queue = [] 590 | , frameDuration = 1000 / 60 591 | 592 | raf = function(callback) { 593 | if(queue.length === 0) { 594 | var _now = now() 595 | , next = Math.max(0, frameDuration - (_now - last)) 596 | last = next + _now 597 | setTimeout(function() { 598 | var cp = queue.slice(0) 599 | // Clear queue here to prevent 600 | // callbacks from appending listeners 601 | // to the current frame's queue 602 | queue.length = 0 603 | for(var i = 0; i < cp.length; i++) { 604 | if(!cp[i].cancelled) { 605 | try{ 606 | cp[i].callback(last) 607 | } catch(e) { 608 | setTimeout(function() { throw e }, 0) 609 | } 610 | } 611 | } 612 | }, Math.round(next)) 613 | } 614 | queue.push({ 615 | handle: ++id, 616 | callback: callback, 617 | cancelled: false 618 | }) 619 | return id 620 | } 621 | 622 | caf = function(handle) { 623 | for(var i = 0; i < queue.length; i++) { 624 | if(queue[i].handle === handle) { 625 | queue[i].cancelled = true 626 | } 627 | } 628 | } 629 | } 630 | 631 | module.exports = function(fn) { 632 | // Wrap in a new function to prevent 633 | // `cancel` potentially being assigned 634 | // to the native rAF function 635 | return raf.call(root, fn) 636 | } 637 | module.exports.cancel = function() { 638 | caf.apply(root, arguments) 639 | } 640 | module.exports.polyfill = function() { 641 | root.requestAnimationFrame = raf 642 | root.cancelAnimationFrame = caf 643 | } 644 | 645 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(5))) 646 | 647 | /***/ }), 648 | /* 5 */ 649 | /***/ (function(module, exports) { 650 | 651 | var g; 652 | 653 | // This works in non-strict mode 654 | g = (function() { 655 | return this; 656 | })(); 657 | 658 | try { 659 | // This works if eval is allowed (see CSP) 660 | g = g || Function("return this")() || (1,eval)("this"); 661 | } catch(e) { 662 | // This works if the window reference is available 663 | if(typeof window === "object") 664 | g = window; 665 | } 666 | 667 | // g can still be undefined, but nothing to do about it... 668 | // We return undefined, instead of nothing here, so it's 669 | // easier to handle this case. if(!global) { ...} 670 | 671 | module.exports = g; 672 | 673 | 674 | /***/ }) 675 | /******/ ]); --------------------------------------------------------------------------------