├── .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 | [](https://github.com/ArtBIT/mouse-follower) [](https://github.com/ArtBIT/mouse-follower) [](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 | [](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 |
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 | /******/ ]);
--------------------------------------------------------------------------------