├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── dist
├── usfl.js
├── usfl.js.map
└── usfl.min.js
├── docs
└── README.md
├── karma.conf.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── array
│ ├── array.js
│ ├── clone.js
│ ├── index.js
│ ├── move-element.js
│ ├── nearest.js
│ ├── random-choice.js
│ ├── sort-alpha.js
│ ├── sort-numbered.js
│ ├── sort-numeric.js
│ ├── sort-random.js
│ └── unique.js
├── dom
│ ├── block-scrolling.js
│ ├── el-coords.js
│ ├── force-redraw.js
│ ├── get-page-height.js
│ ├── get-scroll-percentage.js
│ ├── get-scroll-remaining.js
│ ├── get-scroll-top.js
│ ├── get-srcset-image.js
│ ├── index.js
│ ├── is-element-in-viewport.js
│ ├── is-page-end.js
│ ├── resize.js
│ ├── scroll.js
│ ├── set-style.js
│ └── transition-end.js
├── ease
│ ├── back.js
│ ├── bounce.js
│ ├── circular.js
│ ├── cubic.js
│ ├── elastic.js
│ ├── expo.js
│ ├── index.js
│ ├── linear.js
│ ├── quad.js
│ ├── quart.js
│ ├── quint.js
│ └── sine.js
├── events
│ ├── debounce.js
│ ├── delegate-events.js
│ ├── emitter.js
│ ├── event-bus.js
│ ├── heartbeat.js
│ └── index.js
├── fps
│ └── index.js
├── fullscreen
│ ├── api.js
│ └── index.js
├── graphics
│ ├── download-image.js
│ ├── get-image-data-url.js
│ ├── index.js
│ ├── open-image.js
│ ├── shape.js
│ └── spritesheet-player.js
├── gui
│ └── index.js
├── http
│ ├── get-location.js
│ ├── index.js
│ ├── jsonp.js
│ ├── load-script.js
│ ├── url-params.js
│ └── xhr.js
├── index.js
├── input
│ ├── click-outside.js
│ ├── index.js
│ ├── key-input.js
│ ├── keyboard.js
│ ├── microphone.js
│ ├── mouse-left-window.js
│ ├── mouse-wheel.js
│ ├── pointer-coords.js
│ └── touch-input.js
├── linked-list
│ └── index.js
├── loop
│ └── index.js
├── math
│ ├── angle.js
│ ├── cerp.js
│ ├── circle-distribution.js
│ ├── clamp.js
│ ├── coin-toss.js
│ ├── cross-product-2d.js
│ ├── degrees.js
│ ├── difference.js
│ ├── distance-sq.js
│ ├── distance.js
│ ├── dot-product-2d.js
│ ├── get-circle-points.js
│ ├── get-intersection-area.js
│ ├── get-overlap-x.js
│ ├── get-overlap-y.js
│ ├── index.js
│ ├── lerp.js
│ ├── map.js
│ ├── normalize.js
│ ├── orientation.js
│ ├── percent-remaining.js
│ ├── perspective.js
│ ├── quadratic-curve.js
│ ├── radians.js
│ ├── random-int.js
│ ├── random-sign.js
│ ├── random.js
│ ├── rotate-point.js
│ ├── rotate-to-deg.js
│ ├── rotate-to-rad.js
│ ├── round-to-nearest.js
│ ├── round-to.js
│ ├── size.js
│ ├── smerp.js
│ ├── smoothstep.js
│ ├── split-value-and-unit.js
│ ├── weighted-average.js
│ └── weighted-distribution.js
├── media
│ ├── can-play.js
│ ├── cuepoints-reader.js
│ ├── index.js
│ ├── ios-play-video-inline.js
│ ├── video-player.js
│ ├── vimeo.js
│ ├── youtube-basic.js
│ └── youtube.js
├── object-pool
│ └── index.js
├── object
│ ├── clone.js
│ ├── filter.js
│ ├── index.js
│ └── map.js
├── particle
│ ├── index.js
│ └── particle-group.js
├── platform
│ ├── android-native.js
│ ├── android-version.js
│ ├── android.js
│ ├── chrome-ios.js
│ ├── desktop.js
│ ├── device-orientation.js
│ ├── firefox.js
│ ├── ie-version.js
│ ├── ie.js
│ ├── index.js
│ ├── ios-version.js
│ ├── ios.js
│ ├── ipad.js
│ ├── iphone.js
│ ├── language.js
│ ├── linux.js
│ ├── local-host.js
│ ├── mac.js
│ ├── mobile.js
│ ├── mp4.js
│ ├── safari-ios.js
│ ├── safari.js
│ ├── screen.js
│ ├── webgl.js
│ ├── webm.js
│ ├── windows-phone.js
│ └── windows.js
├── polyfill
│ ├── class-list.js
│ ├── console.js
│ ├── index.js
│ └── request-animation-frame.js
├── popup
│ └── index.js
├── quad-tree
│ └── index.js
├── share
│ ├── email.js
│ ├── facebook.js
│ ├── googleplus.js
│ ├── index.js
│ ├── linkedin.js
│ ├── pinterest.js
│ ├── reddit.js
│ ├── renren.js
│ ├── sms.js
│ ├── twitter.js
│ ├── vkontakte.js
│ ├── weibo.js
│ └── whatsapp.js
├── storage
│ └── index.js
├── string
│ ├── after-first.js
│ ├── after-last.js
│ ├── before-first.js
│ ├── before-last.js
│ ├── begins-with.js
│ ├── between.js
│ ├── block.js
│ ├── capitalize.js
│ ├── count-of.js
│ ├── edit-distance.js
│ ├── ends-with.js
│ ├── escape-html.js
│ ├── escape-pattern.js
│ ├── has-text.js
│ ├── index.js
│ ├── is-numeric.js
│ ├── pad-left.js
│ ├── pad-right.js
│ ├── prevent-widow.js
│ ├── proper-case.js
│ ├── remove-extra-whitespace.js
│ ├── remove.js
│ ├── reverse-words.js
│ ├── reverse.js
│ ├── similarity.js
│ ├── strip-tags.js
│ ├── swap-case.js
│ ├── time-code.js
│ ├── to-number.js
│ ├── truncate.js
│ └── word-count.js
├── track
│ ├── event.js
│ ├── index.js
│ ├── load.js
│ └── pageview.js
├── tween
│ └── index.js
└── visibility
│ ├── api.js
│ └── index.js
└── test
├── array.spec.js
├── bundle-nodejs.spec.js
├── bundle.spec.js
├── dom.spec.js
├── ease.spec.js
├── events.spec.js
├── fps.spec.js
├── fullscreen.spec.js
├── graphics.spec.js
├── http.spec.js
├── input.spec.js
├── linked-list.spec.js
├── loop.spec.js
├── math.spec.js
├── media.spec.js
├── object-pool.spec.js
├── object.spec.js
├── particle.spec.js
├── platform.spec.js
├── popup.spec.js
├── quad-tree.spec.js
├── share.spec.js
├── storage.spec.js
├── string.spec.js
├── track.spec.js
├── tween.spec.js
└── visibility.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "es2015", {
5 | "loose": true
6 | }
7 | ]
8 | ],
9 | "plugins": [
10 | ["external-helpers"],
11 | ["transform-runtime", {
12 | "helpers": true,
13 | "polyfill": false,
14 | "regenerator": false,
15 | "moduleName": "babel-runtime"
16 | }]
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [{.travis.yml,package.json,bower.json}]
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "es6": true,
6 | "mocha": true,
7 | "node": true
8 | },
9 | "globals": {
10 | "expect": true,
11 | "it": true,
12 | "usfl": true
13 | },
14 | "plugins": [],
15 | "parserOptions": {
16 | "ecmaVersion": 6,
17 | "jsx": true,
18 | "sourceType": "module"
19 | },
20 | "rules": {
21 | "array-bracket-spacing": [2, "never"],
22 | "block-scoped-var": 0,
23 | "block-spacing": 2,
24 | "brace-style": [2, "1tbs"],
25 | "callback-return": [2, ["cb", "callback", "next"]],
26 | "camelcase": [2, {"properties": "never"}],
27 | "comma-dangle": [2, "never"],
28 | "comma-spacing": 2,
29 | "comma-style": [2, "last"],
30 | "consistent-return": 2,
31 | "curly": [2, "all"],
32 | "default-case": 2,
33 | "dot-location": [2, "property"],
34 | "dot-notation": [2, {"allowKeywords": true}],
35 | "eol-last": 2,
36 | "eqeqeq": 2,
37 | "func-style": [2, "declaration", {"allowArrowFunctions": true}],
38 | "guard-for-in": 2,
39 | "indent": [2, 4, {"SwitchCase": 1}],
40 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
41 | "keyword-spacing": 2,
42 | "max-len": [1, 120],
43 | "new-cap": 0,
44 | "new-parens": 2,
45 | "no-alert": 2,
46 | "no-array-constructor": 2,
47 | "no-caller": 2,
48 | "no-confusing-arrow": 2,
49 | "no-console": 0,
50 | "no-const-assign": 2,
51 | "no-constant-condition": 2,
52 | "no-delete-var": 2,
53 | "no-empty": [2, {"allowEmptyCatch": true}],
54 | "no-eval": 2,
55 | "no-extend-native": 2,
56 | "no-extra-bind": 2,
57 | "no-extra-semi": 2,
58 | "no-fallthrough": 2,
59 | "no-floating-decimal": 2,
60 | "no-implied-eval": 2,
61 | "no-invalid-this": 2,
62 | "no-iterator": 2,
63 | "no-label-var": 2,
64 | "no-labels": [2, {"allowLoop": true, "allowSwitch": true}],
65 | "no-lone-blocks": 2,
66 | "no-loop-func": 2,
67 | "no-mixed-spaces-and-tabs": [2, false],
68 | "no-multi-spaces": 2,
69 | "no-multi-str": 2,
70 | "no-native-reassign": 2,
71 | "no-nested-ternary": 2,
72 | "no-new-func": 2,
73 | "no-new-object": 2,
74 | "no-new-wrappers": 2,
75 | "no-new": 2,
76 | "no-octal-escape": 2,
77 | "no-octal": 2,
78 | "no-process-exit": 2,
79 | "no-proto": 2,
80 | "no-redeclare": 2,
81 | "no-return-assign": 2,
82 | "no-script-url": 2,
83 | "no-sequences": 2,
84 | "no-shadow-restricted-names": 2,
85 | "no-shadow": 2,
86 | "no-spaced-func": 2,
87 | "no-trailing-spaces": 2,
88 | "no-undef-init": 2,
89 | "no-undef": 2,
90 | "no-undefined": 2,
91 | "no-underscore-dangle": 0,
92 | "no-unused-expressions": 0,
93 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
94 | "no-use-before-define": 2,
95 | "no-useless-concat": 2,
96 | "no-var": 2,
97 | "no-with": 2,
98 | "object-curly-spacing": [2, "never"],
99 | "prefer-const": 2,
100 | "quotes": [2, "single"],
101 | "radix": 2,
102 | "require-jsdoc": 0,
103 | "semi-spacing": [2, {"before": false, "after": true}],
104 | "semi": 2,
105 | "space-before-blocks": 2,
106 | "space-before-function-paren": [0, "never"],
107 | "space-infix-ops": 2,
108 | "space-unary-ops": [2, {"words": true, "nonwords": false}],
109 | "spaced-comment": [0, "always", {"exceptions": ["-"]}],
110 | "strict": [1, "global"],
111 | "valid-jsdoc": [0, {"prefer": { "return": "returns"}}],
112 | "wrap-iife": 2,
113 | "yoda": [2, "never"]
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 |
3 | .*
4 | !/.babelrc
5 | !/.gitignore
6 | !/.gitattributes
7 | !/.travis.yml
8 | !/.eslintrc
9 | !/.editorconfig
10 | !/.npmignore
11 |
12 | Icon?
13 | ehthumbs.db
14 | Thumbs.db
15 | Desktop.ini
16 |
17 | # VCS
18 |
19 | .svn
20 | *.orig
21 | !.gitinclude
22 |
23 | # IDE
24 |
25 | .settings/
26 | .project
27 | user.properties
28 | .idea?
29 | .metadata/*
30 | *.ignore
31 | *.sublime-*
32 | *~
33 | *.swp
34 | *.swo
35 | npm-debug.log
36 |
37 | # FOLDERS
38 |
39 | /bower_components/
40 | /node_modules/
41 | /tmp/
42 | /array/
43 | /dom/
44 | /ease/
45 | /events/
46 | /fps/
47 | /fullscreen/
48 | /graphics/
49 | /gui/
50 | /http/
51 | /index.js
52 | /input/
53 | /linked-list/
54 | /loop/
55 | /math/
56 | /media/
57 | /object-pool/
58 | /object/
59 | /particle/
60 | /platform/
61 | /polyfill/
62 | /popup/
63 | /quad-tree/
64 | /share/
65 | /storage/
66 | /string/
67 | /track/
68 | /tween/
69 | /visibility/
70 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /src/
2 | /docs/
3 | .babelrc
4 | .eslintrc
5 | .editorconfig
6 | .gitattributes
7 | .travis.yml
8 | karma.conf.js
9 | rollup.config.js
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 6
4 | addons:
5 | firefox: "latest"
6 | before_install:
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 | - sleep 3
10 | before_script:
11 | - npm i -g karma karma-cli eslint babel-eslint
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Ian McGregor
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 | # usfl
2 |
3 | [](http://badge.fury.io/js/usfl) [](http://badge.fury.io/bo/usfl) [](https://travis-ci.org/ianmcgregor/usfl)
4 |
5 | A collection of tested, reusable JS utilities and snippets.
6 |
7 | ### Installation
8 |
9 | ```shell
10 | npm install usfl --save
11 | ```
12 |
13 | ### Usage
14 |
15 | ```javascript
16 | // individual module
17 | import randomChoice from 'usfl/array/random-choice';
18 | const chosen = randomChoice([1, 2, 3]);
19 |
20 | // group
21 | import array from 'usfl/array';
22 | const chosen = array.randomChoice([1, 2, 3]);
23 |
24 | // everything
25 | import usfl from 'usfl';
26 | const chosen = usfl.array.randomChoice([1, 2, 3]);
27 | ```
28 |
29 | ### Docs
30 |
31 | [usfl/array](docs/README.md#array)
32 |
33 | [usfl/dom](docs/README.md#dom)
34 |
35 | [usfl/events](docs/README.md#events)
36 |
37 | [usfl/fps](docs/README.md#fps)
38 |
39 | [usfl/fullscreen](docs/README.md#fullscreen)
40 |
41 | [usfl/graphics](docs/README.md#graphics)
42 |
43 | [usfl/http](docs/README.md#http)
44 |
45 | [usfl/input](docs/README.md#input)
46 |
47 | [usfl/linked-list](docs/README.md#linkedlist)
48 |
49 | [usfl/loop](docs/README.md#loop)
50 |
51 | [usfl/math](docs/README.md#math)
52 |
53 | [usfl/media](docs/README.md#media)
54 |
55 | [usfl/object-pool](docs/README.md#objectpool)
56 |
57 | [usfl/platform](docs/README.md#platform)
58 |
59 | [usfl/polyfill](docs/README.md#polyfill)
60 |
61 | [usfl/popup](docs/README.md#popup)
62 |
63 | [usfl/share](docs/README.md#share)
64 |
65 | [usfl/storage](docs/README.md#storage)
66 |
67 | [usfl/string](docs/README.md#string)
68 |
69 | [usfl/track](docs/README.md#track)
70 |
71 | [usfl/visibility](docs/README.md#visibility)
72 |
73 |
74 | ### Dev setup
75 |
76 | To install dependencies:
77 |
78 | ```
79 | $ npm i
80 | ```
81 |
82 | To run tests:
83 |
84 | ```
85 | $ npm i -g karma-cli
86 | $ karma start
87 | ```
88 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | config.set({
3 |
4 | basePath: '',
5 |
6 | browserConsoleLogOptions: {
7 | level: 'log',
8 | format: '%b %T: %m',
9 | terminal: true
10 | },
11 |
12 | plugins: [
13 | 'karma-mocha',
14 | 'karma-chai',
15 | 'karma-browserify',
16 | 'karma-chrome-launcher',
17 | 'karma-firefox-launcher'
18 | ],
19 |
20 | frameworks: [
21 | 'mocha',
22 | 'chai',
23 | 'browserify'
24 | ],
25 |
26 | files: [
27 | 'dist/usfl.js',
28 | 'test/**/*.spec.js'
29 | ],
30 |
31 | exclude: [
32 | 'test/bundle-nodejs.spec.js'
33 | ],
34 |
35 | preprocessors: {'test/**/*.js': ['browserify']},
36 |
37 | // test results reporter to use
38 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
39 | reporters: ['progress'],
40 |
41 | // web server port
42 | port: 9876,
43 |
44 | // enable / disable colors in the output (reporters and logs)
45 | colors: true,
46 |
47 | // level of logging
48 | /* possible values:
49 | config.LOG_DISABLE
50 | config.LOG_ERROR
51 | config.LOG_WARN
52 | config.LOG_INFO
53 | config.LOG_DEBUG*/
54 | logLevel: config.LOG_INFO,
55 |
56 |
57 | // enable / disable watching file and executing tests whenever any file changes
58 | autoWatch: true,
59 |
60 |
61 | // Start these browsers, currently available:
62 | // - Chrome
63 | // - ChromeCanary
64 | // - Firefox
65 | // - Opera (has to be installed with `npm install karma-opera-launcher`)
66 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
67 | // - PhantomJS
68 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
69 | browsers: [
70 | //'iOS',
71 | 'Chrome',
72 | 'Firefox'
73 | //,'IE11 - Win7'
74 | //,'IE10 - Win7'
75 | ],
76 |
77 | // If browser does not capture in given timeout [ms], kill it
78 | captureTimeout: 60000,
79 |
80 | // Continuous Integration mode
81 | // if true, it capture browsers, run tests and exit
82 | singleRun: false
83 | });
84 | };
85 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "usfl",
3 | "version": "3.2.3",
4 | "description": "A collection of tested, reusable JS utilities and snippets.",
5 | "keywords": [
6 | "lib",
7 | "utility",
8 | "util",
9 | "utilities",
10 | "javascript",
11 | "js"
12 | ],
13 | "author": "ianmcgregor",
14 | "license": "MIT",
15 | "main": "dist/usfl.min.js",
16 | "scripts": {
17 | "prepublish": "babel src --out-dir ./",
18 | "start": "rollup -c -w",
19 | "build": "NODE_ENV=production rollup -c && rollup -c",
20 | "test": "eslint 'src/**/*.js' && mocha 'test/bundle-nodejs.spec.js' && karma start --single-run --browsers Firefox",
21 | "lint": "eslint 'src/**/*.js'; exit 0"
22 | },
23 | "readmeFilename": "README.md",
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/ianmcgregor/usfl"
27 | },
28 | "dependencies": {
29 | "eventemitter3": "^2.0.3"
30 | },
31 | "devDependencies": {
32 | "babel-cli": "^6.26.0",
33 | "babel-core": "^6.26.0",
34 | "babel-eslint": "^8.0.0",
35 | "babel-plugin-external-helpers": "^6.22.0",
36 | "babel-plugin-transform-runtime": "^6.23.0",
37 | "babel-preset-es2015": "^6.24.1",
38 | "babelify": "^7.3.0",
39 | "browserify": "^14.4.0",
40 | "chai": "^4.1.2",
41 | "chalk": "^2.1.0",
42 | "karma": "^1.7.1",
43 | "karma-browserify": "^5.1.1",
44 | "karma-chai": "^0.1.0",
45 | "karma-chrome-launcher": "^2.2.0",
46 | "karma-firefox-launcher": "^1.0.1",
47 | "karma-mocha": "^1.3.0",
48 | "mocha": "^3.5.3",
49 | "rollup": "^0.50.0",
50 | "rollup-plugin-babel": "^3.0.2",
51 | "rollup-plugin-commonjs": "^8.2.1",
52 | "rollup-plugin-node-resolve": "^3.0.0",
53 | "rollup-plugin-strip": "^1.1.1",
54 | "rollup-plugin-uglify": "^2.0.1",
55 | "rollup-watch": "^4.3.1",
56 | "watchify": "^3.9.0"
57 | },
58 | "browserify": {
59 | "transform": [
60 | "babelify"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import commonjs from 'rollup-plugin-commonjs';
3 | import nodeResolve from 'rollup-plugin-node-resolve';
4 | import strip from 'rollup-plugin-strip';
5 | import uglify from 'rollup-plugin-uglify';
6 |
7 | const prod = process.env.NODE_ENV === 'production';
8 |
9 | export default {
10 | input: 'src/index.js',
11 | name: 'usfl',
12 | output: {
13 | file: (prod ? 'dist/usfl.min.js' : 'dist/usfl.js'),
14 | format: 'umd'
15 | },
16 | sourcemap: !prod,
17 | plugins: [
18 | nodeResolve({
19 | jsnext: true,
20 | main: true,
21 | preferBuiltins: false
22 | }),
23 | commonjs({
24 | include: [
25 | 'node_modules/core-js/**',
26 | 'node_modules/eventemitter3/**'
27 | ]
28 | }),
29 | babel({
30 | babelrc: false,
31 | // exclude: 'node_modules/**',
32 | presets: [
33 | ['es2015', {loose: true, modules: false}]
34 | ],
35 | plugins: [
36 | 'external-helpers'
37 | ]
38 | }),
39 | (prod && strip({sourceMap: false})),
40 | (prod && uglify())
41 | ]
42 | };
43 |
--------------------------------------------------------------------------------
/src/array/array.js:
--------------------------------------------------------------------------------
1 | export default function array(length, value) {
2 | const arr = [];
3 | for (let i = 0; i < length; i++) {
4 | const val = typeof value !== 'undefined' ? value : i;
5 | arr.push(val);
6 | }
7 | return arr;
8 | }
9 |
--------------------------------------------------------------------------------
/src/array/clone.js:
--------------------------------------------------------------------------------
1 | export default function clone(arr) {
2 | return arr.slice(0);
3 | }
4 |
--------------------------------------------------------------------------------
/src/array/index.js:
--------------------------------------------------------------------------------
1 | import array from './array';
2 | import clone from './clone';
3 | import moveElement from './move-element';
4 | import nearest from './nearest';
5 | import randomChoice from './random-choice';
6 | import sortAlpha from './sort-alpha';
7 | import sortNumbered from './sort-numbered';
8 | import sortNumeric from './sort-numeric';
9 | import sortRandom from './sort-random';
10 | import unique from './unique';
11 |
12 | export default {
13 | array,
14 | clone,
15 | moveElement,
16 | nearest,
17 | randomChoice,
18 | sortAlpha,
19 | sortNumbered,
20 | sortNumeric,
21 | sortRandom,
22 | unique
23 | };
24 |
--------------------------------------------------------------------------------
/src/array/move-element.js:
--------------------------------------------------------------------------------
1 | export default function moveElement(arr, from, to) {
2 | arr = arr.slice(0);
3 | const removed = arr.splice(from, 1)[0];
4 | const insertAt = to < 0 ? arr.length + to : to;
5 | arr.splice(insertAt, 0, removed);
6 | return arr;
7 | }
8 |
--------------------------------------------------------------------------------
/src/array/nearest.js:
--------------------------------------------------------------------------------
1 | export default function nearest(value, arr) {
2 | let least = Number.MAX_VALUE;
3 | return arr.reduce((result, item) => {
4 | const diff = Math.abs(item - value);
5 | if (diff < least) {
6 | least = diff;
7 | result = item;
8 | }
9 | return result;
10 | }, -1);
11 | }
12 |
--------------------------------------------------------------------------------
/src/array/random-choice.js:
--------------------------------------------------------------------------------
1 | export default function randomChoice(arr) {
2 | return arr[Math.floor(Math.random() * arr.length)];
3 | }
4 |
--------------------------------------------------------------------------------
/src/array/sort-alpha.js:
--------------------------------------------------------------------------------
1 | export default function sortAlpha(a, b) {
2 | if (arguments.length === 1) {
3 | return function(x, y) {
4 | return String(x[a]).toLowerCase() > String(y[a]).toLowerCase() ? 1 : -1;
5 | };
6 | }
7 | return String(a).toLowerCase() > String(b).toLowerCase() ? 1 : -1;
8 | }
9 |
--------------------------------------------------------------------------------
/src/array/sort-numbered.js:
--------------------------------------------------------------------------------
1 | const re = /[^0-9.-]/g;
2 |
3 | function diff(a, b) {
4 | const a1 = a.replace(re, '');
5 | const b1 = b.replace(re, '');
6 | return Number(a1) - Number(b1);
7 | }
8 |
9 | export default function sortNumbered(a, b) {
10 | if (arguments.length === 1) {
11 | return function(x, y) {
12 | return diff(x[a], y[a]);
13 | };
14 | }
15 | return diff(a, b);
16 | }
17 |
--------------------------------------------------------------------------------
/src/array/sort-numeric.js:
--------------------------------------------------------------------------------
1 | export default function sortNumeric(a, b) {
2 | if (arguments.length === 1) {
3 | return function(x, y) {
4 | return Number(x[a]) - Number(y[a]);
5 | };
6 | }
7 | return Number(a) - Number(b);
8 | }
9 |
--------------------------------------------------------------------------------
/src/array/sort-random.js:
--------------------------------------------------------------------------------
1 | export default function sortRandom() {
2 | return Math.random() > 0.5 ? -1 : 1;
3 | }
4 |
--------------------------------------------------------------------------------
/src/array/unique.js:
--------------------------------------------------------------------------------
1 | export default function uniqiue(arr) {
2 | return arr.filter((value, index, self) => self.indexOf(value) === index);
3 | }
4 |
--------------------------------------------------------------------------------
/src/dom/block-scrolling.js:
--------------------------------------------------------------------------------
1 | export default function blockScrolling(value) {
2 | document.body.style.overflow = value ? 'hidden' : '';
3 | }
4 |
--------------------------------------------------------------------------------
/src/dom/el-coords.js:
--------------------------------------------------------------------------------
1 | export default function elCoords(el) {
2 | const box = el.getBoundingClientRect();
3 |
4 | const body = document.body;
5 | const docEl = document.documentElement;
6 |
7 | const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
8 | const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
9 |
10 | const clientTop = docEl.clientTop || body.clientTop || 0;
11 | const clientLeft = docEl.clientLeft || body.clientLeft || 0;
12 |
13 | const top = box.top + scrollTop - clientTop;
14 | const left = box.left + scrollLeft - clientLeft;
15 |
16 | return {
17 | top: Math.round(top),
18 | left: Math.round(left),
19 | x: Math.round(left),
20 | y: Math.round(top)
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/dom/force-redraw.js:
--------------------------------------------------------------------------------
1 | export default function forceRedraw(el) {
2 | const display = el.style.display;
3 | el.style.display = 'none';
4 | el.offsetHeight;
5 | el.style.display = display;
6 | }
7 |
--------------------------------------------------------------------------------
/src/dom/get-page-height.js:
--------------------------------------------------------------------------------
1 | export default function getPageHeight() {
2 | const body = document.body;
3 | const doc = document.documentElement;
4 |
5 | return Math.max(
6 | body.scrollHeight || 0,
7 | body.offsetHeight || 0,
8 | body.clientHeight || 0,
9 | doc.clientHeight || 0,
10 | doc.offsetHeight || 0,
11 | doc.scrollHeight || 0
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/dom/get-scroll-percentage.js:
--------------------------------------------------------------------------------
1 | import getScrollTop from './get-scroll-top';
2 |
3 | export default function getScrollPercentage() {
4 | return (getScrollTop() + window.innerHeight) / document.body.clientHeight;
5 | }
6 |
--------------------------------------------------------------------------------
/src/dom/get-scroll-remaining.js:
--------------------------------------------------------------------------------
1 | import getScrollTop from './get-scroll-top';
2 |
3 | export default function getScrollRemaining() {
4 | const b = document.body;
5 | return Math.abs(getScrollTop() - (b.scrollHeight - b.clientHeight));
6 | }
7 |
--------------------------------------------------------------------------------
/src/dom/get-scroll-top.js:
--------------------------------------------------------------------------------
1 | export default function getScrollTop() {
2 | return window.pageYOffset || document.documentElement.scrollTop;
3 | }
4 |
--------------------------------------------------------------------------------
/src/dom/get-srcset-image.js:
--------------------------------------------------------------------------------
1 | export default function getSrcsetImage(srcset, pixelWidth) {
2 | pixelWidth = pixelWidth || window.innerWidth * (window.devicePixelRatio || 0);
3 |
4 | const set = srcset.split(',')
5 | .map((item) => {
6 | const [url, width] = item.trim().split(/\s+/);
7 | const size = parseInt(width.slice(0, -1), 10);
8 | return {url, size};
9 | })
10 | .sort((a, b) => b.size - a.size);
11 |
12 | if (!set.length) {
13 | return '';
14 | }
15 |
16 | return set.reduce((value, item) => {
17 | return item.size >= pixelWidth ? item.url : value;
18 | }, set[0].url);
19 | }
20 |
--------------------------------------------------------------------------------
/src/dom/index.js:
--------------------------------------------------------------------------------
1 | import blockScrolling from './block-scrolling';
2 | import elCoords from './el-coords';
3 | import forceRedraw from './force-redraw';
4 | import getPageHeight from './get-page-height';
5 | import getScrollPercentage from './get-scroll-percentage';
6 | import getScrollRemaining from './get-scroll-remaining';
7 | import getScrollTop from './get-scroll-top';
8 | import getSrcsetImage from './get-srcset-image';
9 | import isElementInViewport from './is-element-in-viewport';
10 | import isPageEnd from './is-page-end';
11 | import resize from './resize';
12 | import scroll from './scroll';
13 | import setStyle from './set-style';
14 | import transitionEnd from './transition-end';
15 |
16 | export default {
17 | blockScrolling,
18 | elCoords,
19 | forceRedraw,
20 | getPageHeight,
21 | getScrollPercentage,
22 | getScrollRemaining,
23 | getScrollTop,
24 | getSrcsetImage,
25 | isElementInViewport,
26 | isPageEnd,
27 | resize,
28 | scroll,
29 | setStyle,
30 | transitionEnd
31 | };
32 |
--------------------------------------------------------------------------------
/src/dom/is-element-in-viewport.js:
--------------------------------------------------------------------------------
1 | export default function isElementInViewport(el, buffer = 0) {
2 | const rect = el.getBoundingClientRect();
3 | return (
4 | rect.top >= 0 - buffer &&
5 | rect.left >= 0 - buffer &&
6 | rect.bottom <= window.innerHeight + buffer &&
7 | rect.right <= window.innerWidth + buffer
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/dom/is-page-end.js:
--------------------------------------------------------------------------------
1 | import getScrollRemaining from './get-scroll-remaining';
2 |
3 | export default function isPageEnd(buffer = 0) {
4 | return getScrollRemaining() <= buffer;
5 | }
6 |
--------------------------------------------------------------------------------
/src/dom/resize.js:
--------------------------------------------------------------------------------
1 | import eventBus from '../events/event-bus';
2 |
3 | export default function resize({
4 | onResize = () => eventBus.emit('resize'),
5 | callNow = false,
6 | debouceDelay = 500
7 | } = {}) {
8 |
9 | let timeoutId = null;
10 |
11 | function resizeHandler() {
12 | clearTimeout(timeoutId);
13 | timeoutId = window.setTimeout(() => onResize(), debouceDelay);
14 | }
15 |
16 | function start() {
17 | window.addEventListener('resize', resizeHandler, false);
18 | }
19 |
20 | function stop() {
21 | window.removeEventListener('resize', resizeHandler);
22 | }
23 |
24 | start();
25 |
26 | if (callNow) {
27 | resizeHandler();
28 | }
29 |
30 | return {start, stop};
31 | }
32 |
--------------------------------------------------------------------------------
/src/dom/scroll.js:
--------------------------------------------------------------------------------
1 | import eventBus from '../events/event-bus';
2 |
3 | export default function scroll({
4 | onScroll = lastScrollY => eventBus.emit('scroll', lastScrollY),
5 | onScrollEnd = lastScrollY => eventBus.emit('scrollend', lastScrollY),
6 | callNow = false,
7 | endTimeout = 200
8 | } = {}) {
9 |
10 | let lastScrollY = 0;
11 | let ticking = false;
12 | let timeoutId;
13 |
14 | function update() {
15 | clearTimeout(timeoutId);
16 | timeoutId = window.setTimeout(() => onScrollEnd(lastScrollY), endTimeout);
17 |
18 | onScroll(lastScrollY);
19 | ticking = false;
20 | }
21 |
22 | function requestTick() {
23 | if (!ticking) {
24 | window.requestAnimationFrame(update);
25 | ticking = true;
26 | }
27 | }
28 |
29 | function scrollHandler() {
30 | // lastScrollY = window.scrollY;
31 | lastScrollY = window.pageYOffset || document.documentElement.scrollTop;
32 | requestTick();
33 | }
34 |
35 | function start() {
36 | window.addEventListener('scroll', scrollHandler, false);
37 | }
38 |
39 | function stop() {
40 | window.removeEventListener('scroll', scrollHandler);
41 | }
42 |
43 | start();
44 |
45 | if (callNow) {
46 | scrollHandler();
47 | }
48 |
49 | return {start, stop};
50 | }
51 |
--------------------------------------------------------------------------------
/src/dom/set-style.js:
--------------------------------------------------------------------------------
1 | export default function setStyle(el, style) {
2 | Object.keys(style).forEach((prop) => {
3 | el.style[prop] = style[prop];
4 | });
5 | return el;
6 | }
7 |
--------------------------------------------------------------------------------
/src/dom/transition-end.js:
--------------------------------------------------------------------------------
1 | export default function transitionEnd(el, cb, timeout = 1000) {
2 |
3 | let timeoutId = null;
4 |
5 | function handler() {
6 | window.clearTimeout(timeoutId);
7 | el.removeEventListener('transitionend', handler);
8 | el.removeEventListener('webkitTransitionEnd', handler);
9 | cb();
10 | }
11 |
12 | if (typeof el.style.transition !== 'undefined') {
13 | el.addEventListener('transitionend', handler);
14 | } else if (typeof el.style.WebkitTransition !== 'undefined') {
15 | el.addEventListener('webkitTransitionEnd', handler);
16 | }
17 |
18 | timeoutId = window.setTimeout(handler, timeout);
19 | }
20 |
--------------------------------------------------------------------------------
/src/ease/back.js:
--------------------------------------------------------------------------------
1 | function easeInBack(t, b, c, d, s = 1.70158) {
2 | return c * (t /= d) * t * ((s + 1) * t - s) + b;
3 | }
4 |
5 | function easeOutBack(t, b, c, d, s = 1.70158) {
6 | return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
7 | }
8 |
9 | function easeInOutBack(t, b, c, d, s = 1.70158) {
10 | if ((t /= d / 2) < 1) {
11 | return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
12 | }
13 | return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInBack,
18 | easeOut: easeOutBack,
19 | easeInOut: easeInOutBack
20 | };
21 |
22 | export {
23 | easeInBack,
24 | easeOutBack,
25 | easeInOutBack
26 | };
27 |
--------------------------------------------------------------------------------
/src/ease/bounce.js:
--------------------------------------------------------------------------------
1 | function easeOutBounce(t, b, c, d) {
2 | if ((t /= d) < (1 / 2.75)) {
3 | return c * (7.5625 * t * t) + b;
4 | } else if (t < (2 / 2.75)) {
5 | return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
6 | } else if (t < (2.5 / 2.75)) {
7 | return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
8 | }
9 | return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
10 | }
11 |
12 | function easeInBounce(t, b, c, d) {
13 | return c - easeOutBounce(d - t, 0, c, d) + b;
14 | }
15 |
16 | function easeInOutBounce(t, b, c, d) {
17 | if (t < d / 2) {
18 | return easeInBounce(t * 2, 0, c, d) * 0.5 + b;
19 | }
20 | return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
21 | }
22 |
23 | export default {
24 | easeIn: easeInBounce,
25 | easeOut: easeOutBounce,
26 | easeInOut: easeInOutBounce
27 | };
28 |
29 | export {
30 | easeInBounce,
31 | easeOutBounce,
32 | easeInOutBounce
33 | };
34 |
--------------------------------------------------------------------------------
/src/ease/circular.js:
--------------------------------------------------------------------------------
1 | const {sqrt} = Math;
2 |
3 | function easeInCircular(t, b, c, d) {
4 | return -c * (sqrt(1 - (t /= d) * t) - 1) + b;
5 | }
6 |
7 | function easeOutCircular(t, b, c, d) {
8 | return c * sqrt(1 - (t = t / d - 1) * t) + b;
9 | }
10 |
11 | function easeInOutCircular(t, b, c, d) {
12 | if ((t /= d / 2) < 1) {
13 | return -c / 2 * (sqrt(1 - t * t) - 1) + b;
14 | }
15 | return c / 2 * (sqrt(1 - (t -= 2) * t) + 1) + b;
16 | }
17 |
18 | export default {
19 | easeIn: easeInCircular,
20 | easeOut: easeOutCircular,
21 | easeInOut: easeInOutCircular
22 | };
23 |
24 | export {
25 | easeInCircular,
26 | easeOutCircular,
27 | easeInOutCircular
28 | };
29 |
--------------------------------------------------------------------------------
/src/ease/cubic.js:
--------------------------------------------------------------------------------
1 | function easeInCubic(t, b, c, d) {
2 | return c * (t /= d) * t * t + b;
3 | }
4 |
5 | function easeOutCubic(t, b, c, d) {
6 | return c * ((t = t / d - 1) * t * t + 1) + b;
7 | }
8 |
9 | function easeInOutCubic(t, b, c, d) {
10 | if ((t /= d / 2) < 1) {
11 | return c / 2 * t * t * t + b;
12 | }
13 | return c / 2 * ((t -= 2) * t * t + 2) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInCubic,
18 | easeOut: easeOutCubic,
19 | easeInOut: easeInOutCubic
20 | };
21 |
22 | export {
23 | easeInCubic,
24 | easeOutCubic,
25 | easeInOutCubic
26 | };
27 |
--------------------------------------------------------------------------------
/src/ease/elastic.js:
--------------------------------------------------------------------------------
1 | const {abs, asin, PI, pow, sin} = Math;
2 | const PI_2 = PI * 2;
3 |
4 | function easeInElastic(t, b, c, d, a, p) {
5 | let s;
6 | if (t === 0) {
7 | return b;
8 | }
9 | if ((t /= d) === 1) {
10 | return b + c;
11 | }
12 | if (!p) {
13 | p = d * 0.3;
14 | }
15 | if (!a || a < abs(c)) {
16 | a = c;
17 | s = p / 4;
18 | } else {
19 | s = p / PI_2 * asin(c / a);
20 | }
21 | return -(a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * PI_2 / p)) + b;
22 | }
23 |
24 | function easeOutElastic(t, b, c, d, a, p) {
25 | let s;
26 | if (t === 0) {
27 | return b;
28 | }
29 | if ((t /= d) === 1) {
30 | return b + c;
31 | }
32 | if (!p) {
33 | p = d * 0.3;
34 | }
35 | if (!a || a < abs(c)) {
36 | a = c;
37 | s = p / 4;
38 | } else {
39 | s = p / PI_2 * asin(c / a);
40 | }
41 | return (a * pow(2, -10 * t) * sin((t * d - s) * PI_2 / p) + c + b);
42 | }
43 |
44 | function easeInOutElastic(t, b, c, d, a, p) {
45 | let s;
46 | if (t === 0) {
47 | return b;
48 | }
49 | if ((t /= d / 2) === 2) {
50 | return b + c;
51 | }
52 | if (!p) {
53 | p = d * (0.3 * 1.5);
54 | }
55 | if (!a || a < abs(c)) {
56 | a = c;
57 | s = p / 4;
58 | } else {
59 | s = p / PI_2 * asin(c / a);
60 | }
61 | if (t < 1) {
62 | return -0.5 * (a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * PI_2 / p)) + b;
63 | }
64 | return a * pow(2, -10 * (t -= 1)) * sin((t * d - s) * PI_2 / p) * 0.5 + c + b;
65 | }
66 |
67 | export default {
68 | easeIn: easeInElastic,
69 | easeOut: easeOutElastic,
70 | easeInOut: easeInOutElastic
71 | };
72 |
73 | export {
74 | easeInElastic,
75 | easeOutElastic,
76 | easeInOutElastic
77 | };
78 |
--------------------------------------------------------------------------------
/src/ease/expo.js:
--------------------------------------------------------------------------------
1 | const {pow} = Math;
2 |
3 | function easeInExpo(t, b, c, d) {
4 | return t === 0 ? b : c * pow(2, 10 * (t / d - 1)) + b;
5 | }
6 |
7 | function easeOutExpo(t, b, c, d) {
8 | return t === d ? b + c : c * (-pow(2, -10 * t / d) + 1) + b;
9 | }
10 |
11 | function easeInOutExpo(t, b, c, d) {
12 | if (t === 0) {
13 | return b;
14 | }
15 | if (t === d) {
16 | return b + c;
17 | }
18 | if ((t /= d / 2) < 1) {
19 | return c / 2 * pow(2, 10 * (t - 1)) + b;
20 | }
21 | return c / 2 * (-pow(2, -10 * --t) + 2) + b;
22 | }
23 |
24 | export default {
25 | easeIn: easeInExpo,
26 | easeOut: easeOutExpo,
27 | easeInOut: easeInOutExpo
28 | };
29 |
30 | export {
31 | easeInExpo,
32 | easeOutExpo,
33 | easeInOutExpo
34 | };
35 |
--------------------------------------------------------------------------------
/src/ease/index.js:
--------------------------------------------------------------------------------
1 | import back, {easeInBack, easeOutBack, easeInOutBack} from './back';
2 | import bounce, {easeInBounce, easeOutBounce, easeInOutBounce} from './bounce';
3 | import circular, {easeInCircular, easeOutCircular, easeInOutCircular} from './circular';
4 | import cubic, {easeInCubic, easeOutCubic, easeInOutCubic} from './cubic';
5 | import elastic, {easeInElastic, easeOutElastic, easeInOutElastic} from './elastic';
6 | import expo, {easeInExpo, easeOutExpo, easeInOutExpo} from './expo';
7 | import linear, {easeLinear} from './linear';
8 | import quad, {easeInQuad, easeOutQuad, easeInOutQuad} from './quad';
9 | import quart, {easeInQuart, easeOutQuart, easeInOutQuart} from './quart';
10 | import quint, {easeInQuint, easeOutQuint, easeInOutQuint} from './quint';
11 | import sine, {easeInSine, easeOutSine, easeInOutSine} from './sine';
12 |
13 | export default {
14 | back,
15 | bounce,
16 | circular,
17 | cubic,
18 | elastic,
19 | expo,
20 | linear,
21 | quad,
22 | quart,
23 | quint,
24 | sine,
25 | easeLinear,
26 | easeInBack,
27 | easeOutBack,
28 | easeInOutBack,
29 | easeInBounce,
30 | easeOutBounce,
31 | easeInOutBounce,
32 | easeInCircular,
33 | easeOutCircular,
34 | easeInOutCircular,
35 | easeInCubic,
36 | easeOutCubic,
37 | easeInOutCubic,
38 | easeInElastic,
39 | easeOutElastic,
40 | easeInOutElastic,
41 | easeInExpo,
42 | easeOutExpo,
43 | easeInOutExpo,
44 | easeInQuad,
45 | easeOutQuad,
46 | easeInOutQuad,
47 | easeInQuart,
48 | easeOutQuart,
49 | easeInOutQuart,
50 | easeInQuint,
51 | easeOutQuint,
52 | easeInOutQuint,
53 | easeInSine,
54 | easeOutSine,
55 | easeInOutSine
56 | };
57 |
58 | /*
59 | TERMS OF USE - EASING EQUATIONS
60 |
61 | Open source under the BSD License.
62 |
63 | Copyright © 2001 Robert Penner
64 | All rights reserved.
65 |
66 | Redistribution and use in source and binary forms, with or without
67 | modification, are permitted provided that the following conditions are met:
68 |
69 | Redistributions of source code must retain the above copyright notice, this
70 | list of conditions and the following disclaimer.
71 | Redistributions in binary form must reproduce the above copyright notice, this
72 | list of conditions and the following disclaimer in the documentation and/or
73 | other materials provided with the distribution.
74 | Neither the name of the author nor the names of contributors may be used to
75 | endorse or promote products derived from this software without specific
76 | prior written permission.
77 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
78 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
80 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
81 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
82 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
83 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
84 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
85 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
86 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
87 | */
88 |
--------------------------------------------------------------------------------
/src/ease/linear.js:
--------------------------------------------------------------------------------
1 | function easeLinear(t, b, c, d) {
2 | return c * t / d + b;
3 | }
4 |
5 | export default {
6 | easeIn: easeLinear,
7 | easeOut: easeLinear,
8 | easeInOut: easeLinear
9 | };
10 |
11 | export {
12 | easeLinear
13 | };
14 |
--------------------------------------------------------------------------------
/src/ease/quad.js:
--------------------------------------------------------------------------------
1 | function easeInQuad(t, b, c, d) {
2 | return c * (t /= d) * t + b;
3 | }
4 |
5 | function easeOutQuad(t, b, c, d) {
6 | return -c * (t /= d) * (t - 2) + b;
7 | }
8 |
9 | function easeInOutQuad(t, b, c, d) {
10 | if ((t /= d / 2) < 1) {
11 | return c / 2 * t * t + b;
12 | }
13 | return -c / 2 * ((--t) * (t - 2) - 1) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInQuad,
18 | easeOut: easeOutQuad,
19 | easeInOut: easeInOutQuad
20 | };
21 |
22 | export {
23 | easeInQuad,
24 | easeOutQuad,
25 | easeInOutQuad
26 | };
27 |
--------------------------------------------------------------------------------
/src/ease/quart.js:
--------------------------------------------------------------------------------
1 | function easeInQuart(t, b, c, d) {
2 | return c * (t /= d) * t * t * t + b;
3 | }
4 |
5 | function easeOutQuart(t, b, c, d) {
6 | return -c * ((t = t / d - 1) * t * t * t - 1) + b;
7 | }
8 |
9 | function easeInOutQuart(t, b, c, d) {
10 | if ((t /= d / 2) < 1) {
11 | return c / 2 * t * t * t * t + b;
12 | }
13 | return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInQuart,
18 | easeOut: easeOutQuart,
19 | easeInOut: easeInOutQuart
20 | };
21 |
22 | export {
23 | easeInQuart,
24 | easeOutQuart,
25 | easeInOutQuart
26 | };
27 |
--------------------------------------------------------------------------------
/src/ease/quint.js:
--------------------------------------------------------------------------------
1 | function easeInQuint(t, b, c, d) {
2 | return c * (t /= d) * t * t * t * t + b;
3 | }
4 |
5 | function easeOutQuint(t, b, c, d) {
6 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
7 | }
8 |
9 | function easeInOutQuint(t, b, c, d) {
10 | if ((t /= d / 2) < 1) {
11 | return c / 2 * t * t * t * t * t + b;
12 | }
13 | return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInQuint,
18 | easeOut: easeOutQuint,
19 | easeInOut: easeInOutQuint
20 | };
21 |
22 | export {
23 | easeInQuint,
24 | easeOutQuint,
25 | easeInOutQuint
26 | };
27 |
--------------------------------------------------------------------------------
/src/ease/sine.js:
--------------------------------------------------------------------------------
1 | const {cos, PI, sin} = Math;
2 | const PI_D2 = PI / 2;
3 |
4 | function easeInSine(t, b, c, d) {
5 | return -c * cos(t / d * PI_D2) + c + b;
6 | }
7 |
8 | function easeOutSine(t, b, c, d) {
9 | return c * sin(t / d * PI_D2) + b;
10 | }
11 |
12 | function easeInOutSine(t, b, c, d) {
13 | return -c / 2 * (cos(PI * t / d) - 1) + b;
14 | }
15 |
16 | export default {
17 | easeIn: easeInSine,
18 | easeOut: easeOutSine,
19 | easeInOut: easeInOutSine
20 | };
21 |
22 | export {
23 | easeInSine,
24 | easeOutSine,
25 | easeInOutSine
26 | };
27 |
--------------------------------------------------------------------------------
/src/events/debounce.js:
--------------------------------------------------------------------------------
1 | export default function debounce(handler) {
2 | let ticking = false;
3 |
4 | function update(event) {
5 | handler(event);
6 | ticking = false;
7 | }
8 |
9 | function requestTick(event) {
10 | if (!ticking) {
11 | window.requestAnimationFrame(() => update(event));
12 | ticking = true;
13 | }
14 | }
15 |
16 | return requestTick;
17 | }
18 |
--------------------------------------------------------------------------------
/src/events/delegate-events.js:
--------------------------------------------------------------------------------
1 | export default function delegateEvents(parentEl, eventType, filter, fn) {
2 |
3 | if (typeof filter === 'string') {
4 | const tagName = filter.toUpperCase();
5 | filter = target => target.tagName === tagName;
6 | }
7 |
8 | parentEl.addEventListener(eventType, (event) => {
9 | let target = event.target;
10 |
11 | while (target !== parentEl) {
12 | if (filter(target)) {
13 | event.stopImmediatePropagation();
14 | fn(target, event);
15 | break;
16 | }
17 | target = target.parentNode;
18 | }
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/src/events/emitter.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'eventemitter3';
2 |
3 | export default class Emitter extends EventEmitter {
4 | constructor() {
5 | super();
6 | }
7 |
8 | off (type, listener) {
9 | if (listener) {
10 | return this.removeListener(type, listener);
11 | }
12 | if (type) {
13 | return this.removeAllListeners(type);
14 | }
15 | return this.removeAllListeners();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/events/event-bus.js:
--------------------------------------------------------------------------------
1 | import Emitter from './emitter';
2 |
3 | export default new Emitter();
4 |
--------------------------------------------------------------------------------
/src/events/heartbeat.js:
--------------------------------------------------------------------------------
1 | import Emitter from './emitter';
2 |
3 | export default function heartbeat(interval) {
4 | let beat = null,
5 | time = 0,
6 | numTimes = 0,
7 | maxTimes = 0,
8 | running = false;
9 |
10 | function start(maxNumTimes = 0, timeOffset = 0) {
11 | maxTimes = maxNumTimes;
12 | time = timeOffset;
13 | numTimes = 0;
14 | running = true;
15 | return beat;
16 | }
17 |
18 | function stop() {
19 | running = false;
20 | return beat;
21 | }
22 |
23 | function update(dt = 1) {
24 | if (!running) {
25 | return beat;
26 | }
27 |
28 | if (maxTimes > 0 && numTimes >= maxTimes) {
29 | running = false;
30 | beat.emit('complete');
31 | return beat;
32 | }
33 |
34 | time += dt;
35 |
36 | if (time >= interval) {
37 | time = 0;
38 | numTimes++;
39 | beat.emit('update', numTimes);
40 | }
41 | return beat;
42 | }
43 |
44 | function setInterval(value) {
45 | interval = value;
46 | return beat;
47 | }
48 |
49 | beat = Object.assign(new Emitter(), {
50 | start,
51 | stop,
52 | update,
53 | setInterval
54 | });
55 |
56 | Object.defineProperties(beat, {
57 | interval: {
58 | get() {
59 | return interval;
60 | },
61 | set(value) {
62 | interval = value;
63 | }
64 | }
65 | });
66 |
67 | return beat;
68 | }
69 |
--------------------------------------------------------------------------------
/src/events/index.js:
--------------------------------------------------------------------------------
1 | import debounce from './debounce';
2 | import delegateEvents from './delegate-events';
3 | import emitter from './emitter';
4 | import eventBus from './event-bus';
5 | import heartbeat from './heartbeat';
6 |
7 | export default {
8 | debounce,
9 | delegateEvents,
10 | emitter,
11 | eventBus,
12 | heartbeat
13 | };
14 |
--------------------------------------------------------------------------------
/src/fps/index.js:
--------------------------------------------------------------------------------
1 | let time = 0;
2 | let fps = 0;
3 | let currentFps = 0;
4 | let averageFps = 0;
5 | let ticks = 0;
6 | let totalFps = 0;
7 | let lastFps = 0;
8 | let lastAverage = 0;
9 | let logMsg = null;
10 |
11 | const el = document.createElement('div');
12 | el.setAttribute('id', 'fps');
13 | el.style.fontFamily = 'monospace';
14 | el.style.position = 'fixed';
15 | el.style.left = '0';
16 | el.style.top = '0';
17 | el.style.padding = '2px 6px';
18 | el.style.zIndex = '99999';
19 | el.style.background = '#000';
20 | el.style.color = '#fff';
21 | el.style.fontSize = '10px';
22 | el.style.userSelect = 'none';
23 |
24 | function report() {
25 | lastFps = currentFps;
26 | lastAverage = averageFps;
27 | el.innerHTML = `FPS: ${currentFps}
AVE: ${averageFps}`;
28 |
29 | if (logMsg) {
30 | el.innerHTML = `${el.innerHTML}
MSG: ${logMsg}`;
31 | }
32 | }
33 |
34 | function update(now) {
35 | if (!el.parentElement) {
36 | document.body.appendChild(el);
37 | }
38 |
39 | if (typeof now === 'undefined') {
40 | now = Date.now();
41 | }
42 |
43 | if (time === 0) {
44 | time = now;
45 | }
46 |
47 | if (now - 1000 > time) {
48 | time = now;
49 | currentFps = fps;
50 | fps = 0;
51 |
52 | if (currentFps > 1) {
53 | ticks++;
54 | totalFps += currentFps;
55 | averageFps = Math.floor(totalFps / ticks);
56 | }
57 |
58 | if (currentFps !== lastFps || averageFps !== lastAverage) {
59 | report();
60 | }
61 | }
62 |
63 | fps++;
64 | }
65 |
66 | function auto() {
67 | window.requestAnimationFrame(auto);
68 | update();
69 | }
70 |
71 | function log(value) {
72 | logMsg = String(value);
73 | report();
74 | }
75 |
76 | function style(props) {
77 | Object.keys(props).forEach((prop) => {
78 | el.style[prop] = props[prop];
79 | });
80 | }
81 |
82 | export default {
83 | auto,
84 | el,
85 | log,
86 | style,
87 | update
88 | };
89 |
--------------------------------------------------------------------------------
/src/fullscreen/api.js:
--------------------------------------------------------------------------------
1 | let request = null,
2 | exit = null,
3 | change = null,
4 | error = null,
5 | element = null,
6 | enabled = null;
7 |
8 | const docEl = (typeof document !== 'undefined' && document.documentElement) || {};
9 |
10 | if (typeof docEl.requestFullscreen !== 'undefined') {
11 | request = 'requestFullscreen';
12 | exit = 'exitFullscreen';
13 | change = 'fullscreenchange';
14 | error = 'fullscreenerror';
15 | element = 'fullscreenElement';
16 | enabled = 'fullscreenEnabled';
17 | } else if (typeof docEl.mozRequestFullScreen !== 'undefined') {
18 | request = 'mozRequestFullScreen';
19 | exit = 'mozCancelFullScreen';
20 | change = 'mozfullscreenchange';
21 | error = 'mozfullscreenerror';
22 | element = 'mozFullScreenElement';
23 | enabled = 'mozFullScreenEnabled';
24 | } else if (typeof docEl.msRequestFullscreen !== 'undefined') {
25 | request = 'msRequestFullscreen';
26 | exit = 'msExitFullscreen';
27 | change = 'MSFullscreenChange';
28 | error = 'MSFullscreenError';
29 | element = 'msFullscreenElement';
30 | enabled = 'msFullscreenEnabled';
31 | } else if (typeof docEl.webkitRequestFullscreen !== 'undefined') {
32 | request = 'webkitRequestFullscreen';
33 | exit = 'webkitExitFullscreen';
34 | change = 'webkitfullscreenchange';
35 | error = 'webkitFullscreenError';
36 | element = 'webkitFullscreenElement';
37 | enabled = 'webkitFullscreenEnabled';
38 | }
39 |
40 | export default {
41 | request,
42 | exit,
43 | change,
44 | error,
45 | element,
46 | enabled
47 | };
48 |
--------------------------------------------------------------------------------
/src/fullscreen/index.js:
--------------------------------------------------------------------------------
1 | import api from './api';
2 | import Emitter from '../events/emitter';
3 |
4 | const fullscreen = new Emitter();
5 |
6 | if (typeof document !== 'undefined') {
7 | document.addEventListener(api.change, (event) => {
8 | fullscreen.emit('change', event);
9 | });
10 |
11 | document.addEventListener(api.error, (event) => {
12 | fullscreen.emit('error', event);
13 | });
14 | }
15 |
16 | Object.defineProperties(fullscreen, {
17 | request: {
18 | value: function(el) {
19 | el = el || document.documentElement;
20 | el[api.request](true);
21 | }
22 | },
23 | exit: {
24 | value: function() {
25 | document[api.exit]();
26 | }
27 | },
28 | toggle: {
29 | value: function(el) {
30 | if (this.isFullscreen) {
31 | this.exit();
32 | } else {
33 | this.request(el);
34 | }
35 | }
36 | },
37 | isSupported: {
38 | get() {
39 | return !!api.request;
40 | }
41 | },
42 | isFullscreen: {
43 | get() {
44 | return !!document[api.element];
45 | }
46 | },
47 | element: {
48 | enumerable: true,
49 | get() {
50 | return document[api.element];
51 | }
52 | },
53 | enabled: {
54 | enumerable: true,
55 | get() {
56 | return document[api.enabled];
57 | }
58 | }
59 | });
60 |
61 | export default fullscreen;
62 |
--------------------------------------------------------------------------------
/src/graphics/download-image.js:
--------------------------------------------------------------------------------
1 | export default function downloadImage(canvas) {
2 | window.location.href = canvas.toDataURL('image/png')
3 | .replace('image/png', 'image/octet-stream');
4 | }
5 |
--------------------------------------------------------------------------------
/src/graphics/get-image-data-url.js:
--------------------------------------------------------------------------------
1 | // convert image to localstorage friendly data URL string
2 | export default function getImageDataURL(img, width, height) {
3 | const canvas = document.createElement('canvas');
4 | const context = canvas.getContext('2d');
5 | canvas.width = width || img.width;
6 | canvas.height = height || img.height;
7 | context.drawImage(img, 0, 0);
8 | return canvas.toDataURL('image/png');
9 | }
10 |
--------------------------------------------------------------------------------
/src/graphics/index.js:
--------------------------------------------------------------------------------
1 | function getColour(r, g, b, a = 1) {
2 | if (typeof r === 'string') {
3 | return r;
4 | }
5 | if (typeof r === 'number') {
6 | return `rgba(${r},${b},${g},${a})`;
7 | }
8 | return null;
9 | }
10 |
11 | export default class Graphics {
12 | constructor(width, height) {
13 | if (typeof width === 'object' && width.tagName === 'CANVAS') {
14 | this.canvas = width;
15 | } else {
16 | this.canvas = document.createElement('canvas');
17 | this.size(width, height);
18 | }
19 | this.ctx = this.canvas.getContext('2d');
20 | }
21 |
22 | get alpha() {
23 | return this.ctx.globalAlpha;
24 | }
25 |
26 | set alpha(value) {
27 | this.ctx.globalAlpha = value;
28 | }
29 |
30 | get blendMode() {
31 | return this.ctx.globalCompositeOperation;
32 | }
33 |
34 | set blendMode(value) {
35 | this.ctx.globalCompositeOperation = value;
36 | }
37 |
38 | get context() {
39 | return this.ctx;
40 | }
41 |
42 | fill(r, g, b, a = 1) {
43 | this.ctx.fillStyle = getColour(r, g, b, a);
44 | return this;
45 | }
46 |
47 | stroke(r, g, b, a = 1) {
48 | this.ctx.strokeStyle = getColour(r, g, b, a);
49 | return this;
50 | }
51 |
52 | circle(x, y, radius) {
53 | const {ctx} = this;
54 | ctx.beginPath();
55 | ctx.arc(x, y, radius, 0, Math.PI * 2, false);
56 | ctx.fill();
57 | return this;
58 | }
59 |
60 | rect(x, y, width, height, angle = 0) {
61 | const {ctx} = this;
62 | if (angle !== 0) {
63 | ctx.save();
64 | ctx.translate(x + width / 2, y + height / 2);
65 | ctx.rotate(angle);
66 | ctx.beginPath();
67 | ctx.rect(-width / 2, -height / 2, width, height);
68 | ctx.fill();
69 | ctx.stroke();
70 | ctx.restore();
71 | } else {
72 | ctx.rect(x, y, width, height);
73 | ctx.fill();
74 | ctx.stroke();
75 | }
76 | return this;
77 | }
78 |
79 | line(x1, y1, x2, y2) {
80 | const {ctx} = this;
81 | ctx.beginPath();
82 | ctx.moveTo(x1, y1);
83 | ctx.lineTo(x2, y2);
84 | ctx.stroke();
85 | return this;
86 | }
87 |
88 | lineWidth(width) {
89 | this.ctx.lineWidth = width;
90 | return this;
91 | }
92 |
93 | move(x, y) {
94 | this.ctx.moveTo(x, y);
95 | return this;
96 | }
97 |
98 | image(el, x, y, options) {
99 | const {ctx} = this;
100 | if (options) {
101 | const {alpha = 1, rotation = 0, scale = 1} = options;
102 | const offsetX = el.width / 2;
103 | const offsetY = el.height / 2;
104 | ctx.save();
105 | ctx.translate(x + offsetX, y + offsetY);
106 | ctx.rotate(rotation);
107 | ctx.scale(scale, scale);
108 | ctx.globalAlpha = alpha;
109 | ctx.drawImage(el, -offsetX, -offsetY);
110 | ctx.restore();
111 | } else {
112 | ctx.drawImage(el, x, y);
113 | }
114 | return this;
115 | }
116 |
117 | text(str, x, y) {
118 | this.ctx.fillText(str, x, y);
119 | return this;
120 | }
121 |
122 | setFontStyle(family, size) {
123 | this.ctx.font = `${size}px ${family}`;
124 | }
125 |
126 | getImageData(x = 0, y = 0, width, height) {
127 | const {ctx, canvas} = this;
128 | return ctx.getImageData(x, y, width || canvas.width, height || canvas.height);
129 | }
130 |
131 | getPixel(x, y) {
132 | x = Math.floor(x);
133 | y = Math.floor(y);
134 | const {data} = this.ctx.getImageData(x, y, 1, 1);
135 | return Array.prototype.slice.call(data);
136 | }
137 |
138 | setPixel(x, y, r, g, b, a) {
139 | x = Math.floor(x);
140 | y = Math.floor(y);
141 | const {width, data} = this.getImageData();
142 | const i = (x + y * width) * 4;
143 | data[i + 0] = r;
144 | data[i + 1] = g;
145 | data[i + 2] = b;
146 | data[i + 3] = a;
147 | return this;
148 | }
149 |
150 | clearCircle(x, y, radius = 20) {
151 | const {ctx} = this;
152 | ctx.save();
153 | ctx.globalCompositeOperation = 'destination-out';
154 | this.circle(x, y, radius);
155 | ctx.globalCompositeOperation = 'source-over';
156 | ctx.restore();
157 | return this;
158 | }
159 |
160 | translateAnd(x, y, fn) {
161 | const {ctx} = this;
162 | ctx.save();
163 | ctx.translate(x, y);
164 | fn(ctx);
165 | ctx.restore();
166 | return this;
167 | }
168 |
169 | clear(r, g, b, a = 1) {
170 | const color = getColour(r, g, b, a);
171 | const {ctx} = this;
172 | const {width, height} = this.canvas;
173 | ctx.save();
174 | ctx.setTransform(1, 0, 0, 1, 0, 0);
175 | if (color) {
176 | ctx.fillStyle = color;
177 | ctx.fillRect(0, 0, width, height);
178 | } else {
179 | ctx.clearRect(0, 0, width, height);
180 | }
181 | ctx.beginPath();
182 | ctx.restore();
183 | return this;
184 | }
185 |
186 | size(width = window.innerWidth, height = window.innerHeight) {
187 | this.canvas.width = width;
188 | this.canvas.height = height;
189 | return this;
190 | }
191 |
192 | destroy() {
193 | if (this.canvas.parentElement) {
194 | this.canvas.parentElement.removeChild(this.canvas);
195 | }
196 | this.canvas = null;
197 | this.ctx = null;
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/graphics/open-image.js:
--------------------------------------------------------------------------------
1 | export default function openImage(canvas) {
2 | const {width, height} = canvas;
3 | const win = window.open('', 'image');
4 | const src = canvas.toDataURL('image/png');
5 | win.document.write(``);
6 | }
7 |
--------------------------------------------------------------------------------
/src/graphics/shape.js:
--------------------------------------------------------------------------------
1 | function triangle(ctx, x, y, width, height, angle = 0) {
2 | if (angle !== 0) {
3 | ctx.save();
4 | ctx.translate(x, y);
5 | ctx.rotate(angle);
6 | ctx.beginPath();
7 | ctx.moveTo(0 - width / 2, 0 + height / 2);
8 | ctx.lineTo(0, 0 - height / 2);
9 | ctx.lineTo(0 + width / 2, 0 + height / 2);
10 | ctx.closePath();
11 | ctx.stroke();
12 | ctx.fill();
13 | ctx.restore();
14 | } else {
15 | ctx.beginPath();
16 | ctx.moveTo(x - width / 2, y + height / 2);
17 | ctx.lineTo(x, y - height / 2);
18 | ctx.lineTo(x + width / 2, y + height / 2);
19 | ctx.closePath();
20 | ctx.stroke();
21 | ctx.fill();
22 | }
23 | }
24 |
25 | function triangleABC(ctx, x1, y1, x2, y2, x3, y3) {
26 | ctx.beginPath();
27 | ctx.moveTo(x1, y1);
28 | ctx.lineTo(x2, y2);
29 | ctx.lineTo(x3, y3);
30 | ctx.closePath();
31 | ctx.stroke();
32 | ctx.fill();
33 | }
34 |
35 | function cross(ctx, radius) {
36 | ctx.beginPath();
37 | ctx.moveTo(-radius, -radius);
38 | ctx.lineTo(radius, radius);
39 | ctx.moveTo(-radius, radius);
40 | ctx.lineTo(radius, -radius);
41 | ctx.stroke();
42 | }
43 |
44 | export default {
45 | triangle,
46 | triangleABC,
47 | cross
48 | };
49 |
--------------------------------------------------------------------------------
/src/graphics/spritesheet-player.js:
--------------------------------------------------------------------------------
1 | import looper from '../loop';
2 |
3 | const dpr = Math.min(2, typeof window !== 'undefined' && (window.devicePixelRatio || 1));
4 |
5 | export default class SpritesheetPlayer {
6 | constructor({
7 | canvas = (typeof document !== 'undefined' && document.createElement('canvas')),
8 | data = null,
9 | image,
10 | fps = 12,
11 | trim = true,
12 | delay = 0,
13 | repeatDelay = 0,
14 | scale = 1,
15 | loop = true
16 | } = {}) {
17 | this.canvas = canvas;
18 | this.data = data;
19 | this.image = image;
20 | this.trim = trim;
21 | this.interval = 1000 / fps;
22 | this.last = 0;
23 | this.currentFrame = 0;
24 | this.playing = false;
25 | this.delay = delay;
26 | this.loop = loop;
27 | this.repeatDelay = repeatDelay;
28 | this.remainingDelay = delay;
29 | this.scale = scale * dpr;
30 |
31 | this.update = this.update.bind(this);
32 |
33 | if (data) {
34 | this.init(data);
35 | }
36 | }
37 |
38 | play() {
39 | if (this.playing) {
40 | return;
41 | }
42 | this.remainingDelay = this.delay;
43 | this.elapsed = 0;
44 | looper.add(this.update);
45 | looper.start();
46 | this.playing = true;
47 | }
48 |
49 | pause() {
50 | looper.remove(this.looper);
51 | // loop.stop();
52 | this.playing = false;
53 | }
54 |
55 | reset() {
56 | this.currentFrame = 0;
57 | this.remainingDelay = this.delay;
58 | this.elapsed = 0;
59 | this.pause();
60 | this.draw(0);
61 | }
62 |
63 | updateDimensions() {
64 | this.canvas.width = this.w * this.scale;
65 | this.canvas.height = this.h * this.scale;
66 | this.canvas.style.width = `${this.w * this.scale / dpr}px`;
67 | this.canvas.style.height = `${this.h * this.scale / dpr}px`;
68 | }
69 |
70 | setScale(value) {
71 | this.scale = value * dpr;
72 | this.updateDimensions();
73 | this.reset();
74 | }
75 |
76 | init(data) {
77 | const rawFrames = Array.isArray(data.frames) ? data.frames : Object.keys(data.frames).map(i => data.frames[i]);
78 | const trimX = this.trim ? Math.min(...rawFrames.map(f => f.spriteSourceSize.x)) : 0;
79 | const trimY = this.trim ? Math.min(...rawFrames.map(f => f.spriteSourceSize.y)) : 0;
80 | const frames = rawFrames.map(f => Object.assign({}, f.frame, {
81 | tx: f.spriteSourceSize.x - trimX,
82 | ty: f.spriteSourceSize.y - trimY
83 | }));
84 |
85 | if (this.trim) {
86 | this.w = Math.max(...frames.map(f => f.tx + f.w));
87 | this.h = Math.max(...frames.map(f => f.ty + f.h));
88 | } else {
89 | this.w = Math.max(...rawFrames.map(f => f.sourceSize.w));
90 | this.h = Math.max(...rawFrames.map(f => f.sourceSize.h));
91 | }
92 |
93 | this.frames = frames;
94 | this.updateDimensions();
95 | this.ctx = this.canvas.getContext('2d');
96 |
97 | if (typeof this.image === 'string') {
98 | const img = new Image();
99 | img.onload = () => this.setImg(img);
100 | img.src = this.image;
101 | } else {
102 | this.setImg(this.image);
103 | }
104 | }
105 |
106 | setImg(img) {
107 | this.img = img;
108 | this.draw(0);
109 | }
110 |
111 | update(df, dt) {
112 | this.elapsed += dt;
113 | if (this.elapsed < this.interval) {
114 | return;
115 | }
116 | this.elapsed = 0;
117 |
118 | if (this.remainingDelay > 0) {
119 | this.remainingDelay -= this.interval;
120 | return;
121 | }
122 |
123 | if (!this.img) {
124 | return;
125 | }
126 |
127 | let currentFrame = this.currentFrame + 1;
128 |
129 | if (currentFrame === this.frames.length) {
130 | if (!this.loop) {
131 | return;
132 | }
133 | this.remainingDelay = this.repeatDelay;
134 | currentFrame = 0;
135 | }
136 |
137 | this.draw(currentFrame);
138 |
139 | this.currentFrame = currentFrame;
140 | }
141 |
142 | draw(currentFrame) {
143 | // this.ctx.fillStyle = 'red';
144 | // this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
145 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
146 |
147 | const frame = this.frames[currentFrame];
148 |
149 | this.ctx.drawImage(
150 | this.img,
151 | frame.x,
152 | frame.y,
153 | frame.w,
154 | frame.h,
155 | frame.tx * this.scale,
156 | frame.ty * this.scale,
157 | frame.w * this.scale,
158 | frame.h * this.scale
159 | );
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/gui/index.js:
--------------------------------------------------------------------------------
1 | import loadScript from '../http/load-script';
2 | import localHost from '../platform/local-host';
3 |
4 | // example usage:
5 | //
6 | // const opts = {
7 | // friction: 0.9,
8 | // maxSpeed: 1
9 | // };
10 | // gui(true)
11 | // .then((g) => {
12 | // g.add(opts, 'friction', 0.7, 0.999);
13 | // g.add(opts, 'maxSpeed', 0.5, 2).onChange((value) => console.log(value));
14 | // })
15 | // .catch((err) => console.error(err));
16 |
17 | export default function gui(localhostOnly = false) {
18 | if (localhostOnly && !localHost()) {
19 | return new Promise(() => {});
20 | }
21 | return new Promise((resolve, reject) => {
22 | loadScript('https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.1/dat.gui.min.js', (err, src) => {
23 | if (err) {
24 | console.error('Error loading script', src);
25 | reject(new Error('Error loading script'));
26 | return;
27 | }
28 | const g = new window.dat.GUI({autoPlace: true});
29 |
30 | const style = document.createElement('style');
31 | document.head.appendChild(style);
32 | const s = style.sheet;
33 | s.insertRule('.dg.ac {overflow: visible !important; z-index:10000 !important}', 0);
34 | s.insertRule('.dg * {font-size:11px !important}', 0);
35 | s.insertRule('.dg input {font:11px Lucida Grande,sans-serif !important}', 0);
36 |
37 | resolve(g);
38 | });
39 | });
40 | }
41 |
42 | gui.localHost = localHost;
43 |
--------------------------------------------------------------------------------
/src/http/get-location.js:
--------------------------------------------------------------------------------
1 | export default function getLocation(href) {
2 | const l = document.createElement('a');
3 | l.href = href;
4 |
5 | return {
6 | hash: l.hash,
7 | host: l.host,
8 | hostname: l.hostname,
9 | pathname: l.pathname,
10 | port: l.port,
11 | protocol: l.protocol,
12 | search: l.search
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/http/index.js:
--------------------------------------------------------------------------------
1 | import getLocation from './get-location';
2 | import jsonp from './jsonp';
3 | import loadScript from './load-script';
4 | import urlParams from './url-params';
5 | import xhr from './xhr';
6 |
7 | export default {
8 | getLocation,
9 | jsonp,
10 | loadScript,
11 | urlParams,
12 | xhr
13 | };
14 |
--------------------------------------------------------------------------------
/src/http/jsonp.js:
--------------------------------------------------------------------------------
1 | export default function jsonp(url, timeout = 10000) {
2 | return new Promise((resolve, reject) => {
3 | const script = document.createElement('script');
4 | const callback = `jsonp_callback_${Math.round(100000 * Math.random())}`;
5 | const separator = url.indexOf('?') >= 0 ? '&' : '?';
6 |
7 | const timeoutId = window.setTimeout(() => {
8 | window[callback](null, 'jsonp error');
9 | }, timeout);
10 |
11 | window[callback] = function(data, err = null) {
12 | window.clearTimeout(timeoutId);
13 | delete window[callback];
14 | document.body.removeChild(script);
15 | if (err) {
16 | reject(err);
17 | } else {
18 | resolve(data);
19 | }
20 | };
21 |
22 | script.src = `${url}${separator}callback=${callback}`;
23 | document.body.appendChild(script);
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/src/http/load-script.js:
--------------------------------------------------------------------------------
1 | export default function loadScript(src, cb) {
2 | const script = document.createElement('script');
3 | script.src = src;
4 | script.addEventListener('load', () => cb(null, src));
5 | script.addEventListener('error', () => cb(true, src));
6 | document.body.appendChild(script);
7 | return script;
8 | }
9 |
--------------------------------------------------------------------------------
/src/http/url-params.js:
--------------------------------------------------------------------------------
1 | const plus = /\+/g; // match '+' symbol
2 | const search = /([^&=]+)=?([^&]*)/g;
3 |
4 | function decode(str) {
5 | return decodeURIComponent(str.replace(plus, ' '));
6 | }
7 |
8 | export default function urlParams(query) {
9 | query = query || window.location.search.slice(1);
10 |
11 | const params = {};
12 | let match = search.exec(query);
13 | while (match) {
14 | params[decode(match[1])] = decode(match[2]);
15 | match = search.exec(query);
16 | }
17 | return params;
18 | }
19 |
--------------------------------------------------------------------------------
/src/http/xhr.js:
--------------------------------------------------------------------------------
1 | export default function xhr(url, type = 'json') {
2 | const p = new Promise((resolve, reject) => {
3 | const req = new XMLHttpRequest();
4 | req.addEventListener('load', () => {
5 | let response = req.response;
6 | if (type === 'json' && typeof response === 'string') {
7 | response = JSON.parse(response);
8 | }
9 | resolve(response);
10 | });
11 | req.addEventListener('error', () => reject(req.status));
12 | req.open('GET', url);
13 | req.responseType = type;
14 | // req.setRequestHeader('Content-type', 'application/json; charset=utf-8');
15 | req.send();
16 | });
17 | return p;
18 | }
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './polyfill';
2 | import array from './array';
3 | import dom from './dom';
4 | import ease from './ease';
5 | import events from './events';
6 | import fullscreen from './fullscreen';
7 | import graphics from './graphics';
8 | import gui from './gui';
9 | import http from './http';
10 | import input from './input';
11 | import linkedList from './linked-list';
12 | import loop, {Loop} from './loop';
13 | import math from './math';
14 | import media from './media';
15 | import object from './object';
16 | import objectPool from './object-pool';
17 | import Particle from './particle';
18 | import ParticleGroup from './particle/particle-group';
19 | import platform from './platform';
20 | import popup from './popup';
21 | import QuadTree from './quad-tree';
22 | import share from './share';
23 | import storage from './storage';
24 | import string from './string';
25 | import track from './track';
26 | import Tween from './tween';
27 | import visibility from './visibility';
28 |
29 | export default {
30 | array,
31 | dom,
32 | ease,
33 | events,
34 | fullscreen,
35 | graphics,
36 | gui,
37 | http,
38 | input,
39 | linkedList,
40 | loop,
41 | Loop,
42 | math,
43 | media,
44 | object,
45 | objectPool,
46 | Particle,
47 | ParticleGroup,
48 | platform,
49 | popup,
50 | QuadTree,
51 | share,
52 | storage,
53 | string,
54 | Tween,
55 | track,
56 | visibility
57 | };
58 |
--------------------------------------------------------------------------------
/src/input/click-outside.js:
--------------------------------------------------------------------------------
1 | function getTest(el) {
2 | if (Array.isArray(el)) {
3 | return target => el.includes(target);
4 | }
5 | if (typeof el === 'function') {
6 | return target => el(target);
7 | }
8 | return target => target === el;
9 | }
10 |
11 | export default function clickOutside(el, fn) {
12 | const test = getTest(el);
13 |
14 | function onClickOutside(event) {
15 | let target = event.target;
16 | let inside = false;
17 |
18 | while (target && target !== document.body) {
19 | if (test(target)) {
20 | event.stopImmediatePropagation();
21 | inside = true;
22 | break;
23 | }
24 | target = target.parentNode;
25 | }
26 |
27 | if (!inside) {
28 | fn(event);
29 | }
30 | }
31 |
32 | function onTouchOutside(event) {
33 | document.body.removeEventListener('click', onClickOutside);
34 | onClickOutside(event);
35 | }
36 |
37 | function destroy() {
38 | document.body.removeEventListener('click', onClickOutside);
39 | document.body.removeEventListener('touchstart', onTouchOutside);
40 | }
41 |
42 | destroy();
43 |
44 | document.body.addEventListener('click', onClickOutside);
45 | document.body.addEventListener('touchstart', onTouchOutside);
46 |
47 | return {
48 | destroy
49 | };
50 | }
51 |
--------------------------------------------------------------------------------
/src/input/index.js:
--------------------------------------------------------------------------------
1 | import clickOutside from './click-outside';
2 | import keyboard from './keyboard';
3 | import keyInput from './key-input';
4 | import microphone from './microphone';
5 | import mouseLeftWindow from './mouse-left-window';
6 | import mouseWheel from './mouse-wheel';
7 | import pointerCoords from './pointer-coords';
8 | import touchInput from './touch-input';
9 |
10 | export default {
11 | clickOutside,
12 | keyboard,
13 | keyInput,
14 | microphone,
15 | mouseLeftWindow,
16 | mouseWheel,
17 | pointerCoords,
18 | touchInput
19 | };
20 |
--------------------------------------------------------------------------------
/src/input/key-input.js:
--------------------------------------------------------------------------------
1 | import array from '../array/array';
2 | import Emitter from '../events/emitter';
3 | import keyboard from './keyboard';
4 |
5 | export default function keyInput() {
6 | const api = new Emitter();
7 | const keys = array(256, false);
8 |
9 | function emitKey(keyCode) {
10 | const keyName = Object.keys(keyboard).reduce((value, key) => {
11 | return keyboard[key] === keyCode ? key : value;
12 | }, '').toLowerCase();
13 | if (keyName) {
14 | api.emit(keyName.toLowerCase());
15 | }
16 | }
17 |
18 | function onKeyDown(event) {
19 | event.preventDefault();
20 | keys[event.keyCode] = true;
21 | api.emit('keydown', event.keyCode);
22 | emitKey(event.keyCode);
23 | }
24 |
25 | function onKeyUp(event) {
26 | event.preventDefault();
27 | keys[event.keyCode] = false;
28 | api.emit('keyup', event.keyCode);
29 | }
30 |
31 | function add() {
32 | document.addEventListener('keydown', onKeyDown, false);
33 | document.addEventListener('keyup', onKeyUp, false);
34 | }
35 |
36 | function remove() {
37 | document.removeEventListener('keydown', onKeyDown);
38 | document.removeEventListener('keyup', onKeyUp);
39 | }
40 |
41 | function isDown(key) {
42 | return keys[key];
43 | }
44 |
45 | function left() {
46 | return keys[keyboard.LEFT] || keys[keyboard.A];
47 | }
48 |
49 | function right() {
50 | return keys[keyboard.RIGHT] || keys[keyboard.D];
51 | }
52 |
53 | function up() {
54 | return keys[keyboard.UP] || keys[keyboard.W];
55 | }
56 |
57 | function down() {
58 | return keys[keyboard.DOWN] || keys[keyboard.S];
59 | }
60 |
61 | function space() {
62 | return keys[keyboard.SPACE];
63 | }
64 |
65 | function enable(value = true) {
66 | remove();
67 | if (value) {
68 | add();
69 | }
70 | }
71 |
72 | add();
73 |
74 | return Object.assign(api, {
75 | keyboard,
76 | enable,
77 | isDown,
78 | left,
79 | right,
80 | up,
81 | down,
82 | space
83 | });
84 | }
85 |
--------------------------------------------------------------------------------
/src/input/keyboard.js:
--------------------------------------------------------------------------------
1 | export default {
2 | A: 'A'.charCodeAt(0),
3 | B: 'B'.charCodeAt(0),
4 | C: 'C'.charCodeAt(0),
5 | D: 'D'.charCodeAt(0),
6 | E: 'E'.charCodeAt(0),
7 | F: 'F'.charCodeAt(0),
8 | G: 'G'.charCodeAt(0),
9 | H: 'H'.charCodeAt(0),
10 | I: 'I'.charCodeAt(0),
11 | J: 'J'.charCodeAt(0),
12 | K: 'K'.charCodeAt(0),
13 | L: 'L'.charCodeAt(0),
14 | M: 'M'.charCodeAt(0),
15 | N: 'N'.charCodeAt(0),
16 | O: 'O'.charCodeAt(0),
17 | P: 'P'.charCodeAt(0),
18 | Q: 'Q'.charCodeAt(0),
19 | R: 'R'.charCodeAt(0),
20 | S: 'S'.charCodeAt(0),
21 | T: 'T'.charCodeAt(0),
22 | U: 'U'.charCodeAt(0),
23 | V: 'V'.charCodeAt(0),
24 | W: 'W'.charCodeAt(0),
25 | X: 'X'.charCodeAt(0),
26 | Y: 'Y'.charCodeAt(0),
27 | Z: 'Z'.charCodeAt(0),
28 | ZERO: '0'.charCodeAt(0),
29 | ONE: '1'.charCodeAt(0),
30 | TWO: '2'.charCodeAt(0),
31 | THREE: '3'.charCodeAt(0),
32 | FOUR: '4'.charCodeAt(0),
33 | FIVE: '5'.charCodeAt(0),
34 | SIX: '6'.charCodeAt(0),
35 | SEVEN: '7'.charCodeAt(0),
36 | EIGHT: '8'.charCodeAt(0),
37 | NINE: '9'.charCodeAt(0),
38 | NUMPAD_0: 96,
39 | NUMPAD_1: 97,
40 | NUMPAD_2: 98,
41 | NUMPAD_3: 99,
42 | NUMPAD_4: 100,
43 | NUMPAD_5: 101,
44 | NUMPAD_6: 102,
45 | NUMPAD_7: 103,
46 | NUMPAD_8: 104,
47 | NUMPAD_9: 105,
48 | NUMPAD_MULTIPLY: 106,
49 | NUMPAD_ADD: 107,
50 | NUMPAD_ENTER: 108,
51 | NUMPAD_SUBTRACT: 109,
52 | NUMPAD_DECIMAL: 110,
53 | NUMPAD_DIVIDE: 111,
54 | F1: 112,
55 | F2: 113,
56 | F3: 114,
57 | F4: 115,
58 | F5: 116,
59 | F6: 117,
60 | F7: 118,
61 | F8: 119,
62 | F9: 120,
63 | F10: 121,
64 | F11: 122,
65 | F12: 123,
66 | F13: 124,
67 | F14: 125,
68 | F15: 126,
69 | COLON: 186,
70 | EQUALS: 187,
71 | UNDERSCORE: 189,
72 | QUESTION_MARK: 191,
73 | TILDE: 192,
74 | OPEN_BRACKET: 219,
75 | BACKWARD_SLASH: 220,
76 | CLOSED_BRACKET: 221,
77 | QUOTES: 222,
78 | BACKSPACE: 8,
79 | TAB: 9,
80 | CLEAR: 12,
81 | ENTER: 13,
82 | SHIFT: 16,
83 | CONTROL: 17,
84 | ALT: 18,
85 | CAPS_LOCK: 20,
86 | ESC: 27,
87 | SPACE: 32,
88 | PAGE_UP: 33,
89 | PAGE_DOWN: 34,
90 | END: 35,
91 | HOME: 36,
92 | LEFT: 37,
93 | UP: 38,
94 | RIGHT: 39,
95 | DOWN: 40,
96 | INSERT: 45,
97 | DELETE: 46,
98 | HELP: 47,
99 | NUM_LOCK: 144
100 | };
101 |
--------------------------------------------------------------------------------
/src/input/microphone.js:
--------------------------------------------------------------------------------
1 | import Emitter from '../events/emitter';
2 |
3 | export default function microphone() {
4 | const mic = new Emitter();
5 | let stream = null;
6 |
7 | const getUserMedia = (navigator.getUserMedia ||
8 | navigator.webkitGetUserMedia ||
9 | navigator.mozGetUserMedia ||
10 | navigator.msGetUserMedia);
11 |
12 | const isSupported = !!getUserMedia;
13 |
14 | function connect() {
15 | if (!isSupported) {
16 | mic.emit('error', 'Not supported');
17 | return;
18 | }
19 | getUserMedia({
20 | audio: true
21 | }, (mediaStream) => {
22 | stream = mediaStream;
23 | mic.emit('connect', stream);
24 | }, (e) => {
25 | if (e.name === 'PermissionDeniedError' || e === 'PERMISSION_DENIED') {
26 | console.log('Permission denied. Undo by clicking the camera icon in the address bar');
27 | mic.emit('denied');
28 | } else {
29 | mic.emit('error', e.message || e);
30 | }
31 | });
32 | }
33 |
34 | function disconnect() {
35 | if (stream) {
36 | stream.stop();
37 | stream = null;
38 | }
39 | }
40 |
41 | function createWebAudioSource(webAudioContext, connectTo) {
42 | if (!stream) {
43 | return null;
44 | }
45 |
46 | const source = webAudioContext.createMediaStreamSource(stream);
47 |
48 | if (connectTo) {
49 | source.connect(connectTo);
50 | }
51 |
52 | // HACK: stops moz garbage collection killing the stream
53 | // see https://support.mozilla.org/en-US/questions/984179
54 | if (navigator.mozGetUserMedia) {
55 | window.hack_for_mozilla = source;
56 | }
57 |
58 | return source;
59 | }
60 |
61 | return Object.assign(mic, {
62 | connect,
63 | disconnect,
64 | createWebAudioSource,
65 | isSupported: () => isSupported,
66 | stream: () => stream
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/input/mouse-left-window.js:
--------------------------------------------------------------------------------
1 | export default function mouseLeftWindow(fn) {
2 | function handler(event) {
3 | const from = event.relatedTarget || event.toElement;
4 | if (!from || from.nodeName === 'HTML') {
5 | fn(event);
6 | }
7 | }
8 |
9 | document.addEventListener('mouseout', handler, false);
10 |
11 | return {
12 | destroy () {
13 | document.removeEventListener('mouseout', handler);
14 | }
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/input/mouse-wheel.js:
--------------------------------------------------------------------------------
1 | import Emitter from '../events/emitter';
2 |
3 | export default function mouseWheel(speed) {
4 | speed = speed || 2;
5 |
6 | let wheel = null;
7 |
8 | function wheelHandler(event) {
9 | const direction = (event.detail < 0 || event.wheelDelta > 0) ? 1 : -1;
10 | const delta = direction * speed;
11 |
12 | if (direction > 0) {
13 | wheel.emit('up', delta);
14 | } else {
15 | wheel.emit('down', delta);
16 | }
17 |
18 | wheel.emit('update', delta);
19 | }
20 |
21 | function add() {
22 | if ('onmousewheel' in window) {
23 | window.addEventListener('mousewheel', wheelHandler, false);
24 | } else if (window.addEventListener) {
25 | window.addEventListener('DOMMouseScroll', wheelHandler, false);
26 | }
27 | }
28 |
29 | function remove() {
30 | if ('onmousewheel' in window) {
31 | window.removeEventListener('mousewheel', wheelHandler, false);
32 | } else if (window.removeEventListener) {
33 | window.removeEventListener('DOMMouseScroll', wheelHandler, false);
34 | }
35 | }
36 |
37 | add();
38 |
39 | wheel = Object.assign(new Emitter(), {
40 | add,
41 | remove
42 | });
43 |
44 | return wheel;
45 | }
46 |
--------------------------------------------------------------------------------
/src/input/pointer-coords.js:
--------------------------------------------------------------------------------
1 | import getPageHeight from '../dom/get-page-height';
2 |
3 | export default function pointerCoords() {
4 | let self = null;
5 |
6 | function calculateCoords(event) {
7 | const touch = event.touches && event.touches.length;
8 | const p = touch ? event.touches[0] : event;
9 | const cX = p.clientX || 0;
10 | const cY = p.clientY || 0;
11 | const pX = window.pageXOffset;
12 | const pY = window.pageYOffset;
13 | self.event = event;
14 | self.clientX = cX;
15 | self.clientY = cY;
16 | self.x = cX + pX;
17 | self.y = cY + pY;
18 | self.percentX = self.x / window.innerWidth;
19 | self.percentY = self.y / getPageHeight();
20 | }
21 |
22 | self = {
23 | event: null,
24 | clientX: 0,
25 | clientY: 0,
26 | x: 0,
27 | y: 0,
28 | percentX: 0,
29 | percentY: 0,
30 | isListening: false,
31 |
32 | on: function() {
33 | document.body.addEventListener('mousemove', calculateCoords);
34 | document.body.addEventListener('touchmove', calculateCoords);
35 | self.isListening = true;
36 | return this;
37 | },
38 | off: function() {
39 | document.body.removeEventListener('mousemove', calculateCoords);
40 | document.body.removeEventListener('touchmove', calculateCoords);
41 | self.isListening = false;
42 | return this;
43 | }
44 | };
45 | return self;
46 | }
47 |
--------------------------------------------------------------------------------
/src/input/touch-input.js:
--------------------------------------------------------------------------------
1 | import Emitter from '../events/emitter';
2 |
3 | export default function touchInput(el) {
4 | el = el || document.body;
5 |
6 | const data = {
7 | start: [-1, -1],
8 | move: [-1, -1],
9 | end: [-1, -1],
10 | position: [-1, -1],
11 | distance: [0, 0],
12 | direction: ['none', 'none'],
13 | touching: false,
14 | originalEvent: null
15 | };
16 |
17 | let self = null;
18 |
19 | function touchHandler(event) {
20 | if (!(event && event.touches)) {
21 | return;
22 | }
23 | data.originalEvent = event;
24 | const touch = event.touches[0];
25 | const x = touch && touch.pageX;
26 | const y = touch && touch.pageY;
27 |
28 | switch (event.type) {
29 | case 'touchstart':
30 | data.start[0] = data.move[0] = data.end[0] = data.position[0] = x;
31 | data.start[1] = data.move[1] = data.end[1] = data.position[1] = y;
32 | data.touching = true;
33 | self.emit('start', data);
34 | break;
35 | case 'touchmove':
36 | data.move[0] = data.position[0] = x;
37 | data.move[1] = data.position[1] = y;
38 | self.emit('move', data);
39 | break;
40 | case 'touchend':
41 | data.end[0] = data.position[0] = x;
42 | data.end[1] = data.position[1] = y;
43 | data.touching = false;
44 | self.emit('end', data);
45 | break;
46 | default: break;
47 | }
48 | }
49 |
50 | function listen(elem) {
51 | el = elem || el;
52 | el.addEventListener('touchstart', touchHandler);
53 | el.addEventListener('touchmove', touchHandler);
54 | el.addEventListener('touchend', touchHandler);
55 | return self;
56 | }
57 |
58 | function destroy() {
59 | self.removeAllListeners();
60 | el.removeEventListener('touchstart', touchHandler);
61 | el.removeEventListener('touchmove', touchHandler);
62 | el.removeEventListener('touchend', touchHandler);
63 | el = null;
64 | return self;
65 | }
66 |
67 | listen(el);
68 |
69 | self = Object.assign(new Emitter(), {
70 | listen,
71 | isDown: () => data.touching,
72 | getTouch: () => data,
73 | destroy
74 | });
75 |
76 | return self;
77 | }
78 |
--------------------------------------------------------------------------------
/src/linked-list/index.js:
--------------------------------------------------------------------------------
1 | export default function linkedList(arr = []) {
2 |
3 | let first,
4 | last;
5 |
6 | /*
7 | item = {
8 | 'next': null,
9 | 'prev': null
10 | }
11 |
12 | var item = linkedList.getFirst();
13 | while(item) {
14 | // do stuff
15 | item = item.next;
16 | }
17 | */
18 |
19 | function remove(item) {
20 | if (item.next) {
21 | item.next.prev = item.prev;
22 | }
23 | if (item.prev) {
24 | item.prev.next = item.next;
25 | }
26 | if (item === first) {
27 | first = item.next;
28 | }
29 | if (item === last) {
30 | last = item.prev;
31 | }
32 | item.next = item.prev = null;
33 |
34 | return item;
35 | }
36 |
37 | function insertAfter(item, after) {
38 | remove(item);
39 |
40 | item.prev = after;
41 | item.next = after.next;
42 |
43 | if (!after.next) {
44 | last = item;
45 | } else {
46 | after.next.prev = item;
47 | }
48 |
49 | after.next = item;
50 |
51 | return item;
52 | }
53 |
54 | function insertBefore(item, before) {
55 | remove(item);
56 |
57 | item.prev = before.prev;
58 | item.next = before;
59 |
60 | if (!before.prev) {
61 | first = item;
62 | } else {
63 | before.prev.next = item;
64 | }
65 |
66 | before.prev = item;
67 |
68 | return item;
69 | }
70 |
71 | function add(item) {
72 | if (!first) {
73 | first = last = item;
74 | } else {
75 | let i = first;
76 | while (i.next) {
77 | i = i.next;
78 | }
79 | insertAfter(item, i);
80 | }
81 | return item;
82 | }
83 |
84 | function forEach(fn) {
85 | let item = first;
86 | while (item) {
87 | fn(item);
88 | item = item.next;
89 | }
90 | }
91 |
92 | function map(fn) {
93 | const list = linkedList();
94 | let item = first;
95 | while (item) {
96 | list.add(fn(item));
97 | item = item.next;
98 | }
99 | return list;
100 | }
101 |
102 | arr.forEach((item) => add(item));
103 |
104 | return {
105 | get first () {
106 | return first;
107 | },
108 | getFirst () {
109 | return first;
110 | },
111 | get last () {
112 | return last;
113 | },
114 | getLast () {
115 | return last;
116 | },
117 | get length () {
118 | return this.getCount();
119 | },
120 | getCount () {
121 | let count = 0;
122 | let i = first;
123 | while (i) {
124 | count++;
125 | i = i.next;
126 | }
127 | return count;
128 | },
129 | add,
130 | remove,
131 | insertAfter,
132 | insertBefore,
133 | forEach,
134 | map
135 | };
136 | }
137 |
--------------------------------------------------------------------------------
/src/loop/index.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'eventemitter3';
2 |
3 | export class Loop {
4 | constructor() {
5 | this.update = this.update.bind(this);
6 |
7 | this.last = 0;
8 | this.raf = null;
9 | this.running = false;
10 |
11 | this.emitter = new EventEmitter();
12 | }
13 |
14 | start() {
15 | if (this.running) {
16 | return;
17 | }
18 |
19 | this.last = 0;
20 | this.running = true;
21 | this.update();
22 | }
23 |
24 | stop() {
25 | if (!this.running) {
26 | return;
27 | }
28 |
29 | this.running = false;
30 | window.cancelAnimationFrame(this.raf);
31 | }
32 |
33 | update() {
34 | if (!this.running) {
35 | return;
36 | }
37 |
38 | this.raf = window.requestAnimationFrame(this.update);
39 |
40 | const now = Date.now();
41 | const deltaTime = now - this.last;
42 | const deltaFrames = deltaTime * 0.06;
43 | this.last = now;
44 |
45 | this.emitter.emit('update', deltaFrames, deltaTime);
46 | }
47 |
48 | add(fn) {
49 | this.emitter.on('update', fn);
50 | return this;
51 | }
52 |
53 | remove(fn) {
54 | this.emitter.removeListener('update', fn);
55 | }
56 | }
57 |
58 | export default new Loop();
59 |
--------------------------------------------------------------------------------
/src/math/angle.js:
--------------------------------------------------------------------------------
1 | export default function angle(x1, y1, x2, y2) {
2 | const dx = x2 - x1;
3 | const dy = y2 - y1;
4 | return Math.atan2(dy, dx);
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/cerp.js:
--------------------------------------------------------------------------------
1 | export default function cerp(from, to, weight = 0.3) {
2 | const f = (1 - Math.cos(weight * Math.PI)) / 2;
3 | return (from * (1 - f) + to * f);
4 | }
5 |
--------------------------------------------------------------------------------
/src/math/circle-distribution.js:
--------------------------------------------------------------------------------
1 | export default function circleDistribution(radius, origin = {x: 0, y: 0}, p = {x: 0, y: 0}) {
2 | const r = Math.sqrt(Math.random()) * radius;
3 | const theta = Math.random() * Math.PI * 2;
4 | p.x = origin.x + Math.cos(theta) * r;
5 | p.y = origin.y + Math.sin(theta) * r;
6 | return p;
7 | }
8 |
--------------------------------------------------------------------------------
/src/math/clamp.js:
--------------------------------------------------------------------------------
1 | export default function clamp(value, min, max) {
2 | if (min > max) {
3 | const a = min;
4 | min = max;
5 | max = a;
6 | }
7 | if (value < min) {
8 | return min;
9 | }
10 | if (value > max) {
11 | return max;
12 | }
13 | return value;
14 | }
15 |
--------------------------------------------------------------------------------
/src/math/coin-toss.js:
--------------------------------------------------------------------------------
1 | export default function coinToss(heads = true, tails = false) {
2 | return Math.random() > 0.5 ? heads : tails;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/cross-product-2d.js:
--------------------------------------------------------------------------------
1 | /*
2 | The sign tells us if a is to the left (-) or the right (+) of b
3 | */
4 | export default function crossProduct2d(aX, aY, bX, bY) {
5 | return aX * bY - aY * bX;
6 | }
7 |
--------------------------------------------------------------------------------
/src/math/degrees.js:
--------------------------------------------------------------------------------
1 | const DEG = 180 / Math.PI;
2 |
3 | export default function degrees(radians) {
4 | return radians * DEG;
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/difference.js:
--------------------------------------------------------------------------------
1 | export default function difference(a, b) {
2 | return Math.abs(a - b);
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/distance-sq.js:
--------------------------------------------------------------------------------
1 | export default function distanceSQ(x1, y1, x2, y2) {
2 | const dx = x1 - x2;
3 | const dy = y1 - y2;
4 | return dx * dx + dy * dy;
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/distance.js:
--------------------------------------------------------------------------------
1 | export default function distance(x1, y1, x2, y2) {
2 | const dx = x1 - x2;
3 | const dy = y1 - y2;
4 | return Math.sqrt(dx * dx + dy * dy);
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/dot-product-2d.js:
--------------------------------------------------------------------------------
1 | /*
2 | - If A and B are perpendicular (at 90 degrees to each other), the result
3 | of the dot product will be zero, because cos(Θ) will be zero.
4 | - If the angle between A and B are less than 90 degrees, the dot product
5 | will be positive (greater than zero), as cos(Θ) will be positive, and
6 | the vector lengths are always positive values.
7 | - If the angle between A and B are greater than 90 degrees, the dot
8 | product will be negative (less than zero), as cos(Θ) will be negative,
9 | and the vector lengths are always positive values
10 | */
11 | export default function dotProduct2d(aX, aY, bX, bY) {
12 | return aX * bX + aY * bY;
13 | }
14 |
--------------------------------------------------------------------------------
/src/math/get-circle-points.js:
--------------------------------------------------------------------------------
1 | export default function getCirclePoints(originX, originY, radius, count, start, Class) {
2 | if (typeof start === 'undefined') {
3 | start = -Math.PI / 2;
4 | }
5 |
6 | const points = [],
7 | circ = Math.PI * 2,
8 | incr = circ / count;
9 |
10 | for (let i = start; i < circ + start; i += incr) {
11 | const ob = typeof Class === 'undefined' ? {} : new Class();
12 | ob.x = originX + radius * Math.cos(i);
13 | ob.y = originY + radius * Math.sin(i);
14 | points.push(ob);
15 | }
16 |
17 | return points;
18 | }
19 |
--------------------------------------------------------------------------------
/src/math/get-intersection-area.js:
--------------------------------------------------------------------------------
1 | export default function getIntersectionArea(aX, aY, aW, aH, bX, bY, bW, bH) {
2 | const overlapX = Math.max(0, Math.min(aX + aW, bX + bW) - Math.max(aX, bX));
3 | const overlapY = Math.max(0, Math.min(aY + aH, bY + bH) - Math.max(aY, bY));
4 | return overlapX * overlapY;
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/get-overlap-x.js:
--------------------------------------------------------------------------------
1 | export default function getOverlapX(aX, aW, bX, bW) {
2 | return Math.max(0, Math.min(aX + aW, bX + bW) - Math.max(aX, bX));
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/get-overlap-y.js:
--------------------------------------------------------------------------------
1 | export default function getOverlapY(aY, aH, bY, bH) {
2 | return Math.max(0, Math.min(aY + aH, bY + bH) - Math.max(aY, bY));
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/index.js:
--------------------------------------------------------------------------------
1 | import angle from './angle';
2 | import cerp from './cerp';
3 | import circleDistribution from './circle-distribution';
4 | import clamp from './clamp';
5 | import coinToss from './coin-toss';
6 | import crossProduct2d from './cross-product-2d';
7 | import degrees from './degrees';
8 | import difference from './difference';
9 | import distance from './distance';
10 | import distanceSq from './distance-sq';
11 | import dotProduct2d from './dot-product-2d';
12 | import getCirclePoints from './get-circle-points';
13 | import getIntersectionArea from './get-intersection-area';
14 | import getOverlapX from './get-overlap-x';
15 | import getOverlapY from './get-overlap-y';
16 | import lerp from './lerp';
17 | import map from './map';
18 | import normalize from './normalize';
19 | import orientation from './orientation';
20 | import percentRemaining from './percent-remaining';
21 | import perspective from './perspective';
22 | import quadraticCurve from './quadratic-curve';
23 | import radians from './radians';
24 | import random from './random';
25 | import randomInt from './random-int';
26 | import randomSign from './random-sign';
27 | import rotatePoint from './rotate-point';
28 | import rotateToDeg from './rotate-to-deg';
29 | import rotateToRad from './rotate-to-rad';
30 | import roundTo from './round-to';
31 | import roundToNearest from './round-to-nearest';
32 | import size from './size';
33 | import smerp from './smerp';
34 | import smoothstep from './smoothstep';
35 | import splitValueAndUnit from './split-value-and-unit';
36 | import weightedAverage from './weighted-average';
37 | import weightedDistribution from './weighted-distribution';
38 |
39 | export default {
40 | angle,
41 | cerp,
42 | circleDistribution,
43 | clamp,
44 | coinToss,
45 | crossProduct2d,
46 | degrees,
47 | difference,
48 | distance,
49 | distanceSq,
50 | dotProduct2d,
51 | getCirclePoints,
52 | getIntersectionArea,
53 | getOverlapX,
54 | getOverlapY,
55 | lerp,
56 | map,
57 | normalize,
58 | orientation,
59 | percentRemaining,
60 | perspective,
61 | quadraticCurve,
62 | radians,
63 | random,
64 | randomInt,
65 | randomSign,
66 | rotatePoint,
67 | rotateToDeg,
68 | rotateToRad,
69 | roundTo,
70 | roundToNearest,
71 | smerp,
72 | smoothstep,
73 | size,
74 | splitValueAndUnit,
75 | weightedAverage,
76 | weightedDistribution
77 | };
78 |
--------------------------------------------------------------------------------
/src/math/lerp.js:
--------------------------------------------------------------------------------
1 | export default function lerp(from, to, weight = 0.3) {
2 | return from + (to - from) * weight;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/map.js:
--------------------------------------------------------------------------------
1 | export default function map(v, a, b, x, y, clamp = true) {
2 | // value, min expected, max expected, map min, map max, clamp
3 | // e.g. map some value between 0 to 100 to -50 to 50
4 | // map(50, 0, 100, -50, 50) // 0
5 | // map(25, 0, 100, -50, 50) // -25
6 | if (v === a) {
7 | return x;
8 | }
9 |
10 | let val = (v - a) * (y - x) / (b - a) + x;
11 |
12 | if (!clamp) {
13 | return val;
14 | }
15 |
16 | if (y > x) {
17 | if (val > y) {
18 | val = y;
19 | }
20 | if (val < x) {
21 | val = x;
22 | }
23 | } else {
24 | if (val < y) {
25 | val = y;
26 | }
27 | if (val > x) {
28 | val = x;
29 | }
30 | }
31 |
32 | return val;
33 | }
34 |
--------------------------------------------------------------------------------
/src/math/normalize.js:
--------------------------------------------------------------------------------
1 | export default function normalize(value, min, max) {
2 | return (value - min) / (max - min);
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/orientation.js:
--------------------------------------------------------------------------------
1 | export default function orientation(x, y) {
2 | return Math.atan2(y, x);
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/percent-remaining.js:
--------------------------------------------------------------------------------
1 | export default function percentRemaining(value, total) {
2 | return (value % total) / total;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/perspective.js:
--------------------------------------------------------------------------------
1 | // x = x * perspective
2 | // y = y * perspective
3 | // scale = perspective
4 |
5 | export default function perspective(z, focalLength = 300) {
6 | return focalLength / (focalLength + z);
7 | }
8 |
--------------------------------------------------------------------------------
/src/math/quadratic-curve.js:
--------------------------------------------------------------------------------
1 | export default function quadraticCurve(fromX, fromY, cpX, cpY, toX, toY, goThroughCP = false) {
2 | const n = 20;
3 | const points = [fromX, fromY];
4 | let xa = 0;
5 | let ya = 0;
6 |
7 | if (goThroughCP) {
8 | cpX = cpX * 2 - (fromX + toX) / 2;
9 | cpY = cpY * 2 - (fromY + toY) / 2;
10 | }
11 |
12 | for (let i = 1; i <= n; ++i) {
13 | const j = i / n;
14 |
15 | xa = fromX + ((cpX - fromX) * j);
16 | ya = fromY + ((cpY - fromY) * j);
17 |
18 | points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j), ya + (((cpY + ((toY - cpY) * j)) - ya) * j));
19 | }
20 |
21 | return points;
22 | }
23 |
--------------------------------------------------------------------------------
/src/math/radians.js:
--------------------------------------------------------------------------------
1 | const RAD = Math.PI / 180;
2 |
3 | export default function radians(degrees) {
4 | return degrees * RAD;
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/random-int.js:
--------------------------------------------------------------------------------
1 | export default function randomInt(min, max) {
2 | return Math.floor(min + Math.random() * (max - min + 1));
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/random-sign.js:
--------------------------------------------------------------------------------
1 | export default function randomSign() {
2 | return Math.random() > 0.5 ? -1 : 1;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/random.js:
--------------------------------------------------------------------------------
1 | export default function random(min, max) {
2 | if (isNaN(max)) {
3 | max = min;
4 | min = 0;
5 | }
6 | return min + Math.random() * (max - min);
7 | }
8 |
--------------------------------------------------------------------------------
/src/math/rotate-point.js:
--------------------------------------------------------------------------------
1 | export default function rotatePoint(p, theta, origin = {x: 0, y: 0}, p1 = {x: 0, y: 0}) {
2 | const sinTheta = Math.sin(theta);
3 | const cosTheta = Math.cos(theta);
4 | p1.x = (p.x - origin.x) * cosTheta - (p.y - origin.y) * sinTheta;
5 | p1.y = (p.x - origin.x) * sinTheta + (p.y - origin.y) * cosTheta;
6 | p1.x += origin.x;
7 | p1.y += origin.y;
8 | return p1;
9 | }
10 |
--------------------------------------------------------------------------------
/src/math/rotate-to-deg.js:
--------------------------------------------------------------------------------
1 | export default function rotateToDeg(start, end) {
2 | let diff = (end - start) % 360;
3 | if (diff !== diff % 180) {
4 | diff = (diff < 0) ? diff + 360 : diff - 360;
5 | }
6 | return start + diff;
7 | }
8 |
--------------------------------------------------------------------------------
/src/math/rotate-to-rad.js:
--------------------------------------------------------------------------------
1 | const PI2 = Math.PI * 2;
2 |
3 | export default function rotateToRAD(start, end) {
4 | let diff = (end - start) % PI2;
5 | if (diff !== diff % Math.PI) {
6 | diff = diff < 0 ? diff + PI2 : diff - PI2;
7 | }
8 | return start + diff;
9 | }
10 |
--------------------------------------------------------------------------------
/src/math/round-to-nearest.js:
--------------------------------------------------------------------------------
1 | export default function roundToNearest(value, unit) {
2 | return Math.round(value / unit) * unit;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/round-to.js:
--------------------------------------------------------------------------------
1 | export default function roundTo(x, places = 2) {
2 | const div = Math.pow(10, places);
3 | return Math.round(x * div) / div;
4 | }
5 |
--------------------------------------------------------------------------------
/src/math/size.js:
--------------------------------------------------------------------------------
1 | function getScale(method, width, height, areaWidth, areaHeight) {
2 | switch (method) {
3 | case 'cover':
4 | return Math.max(areaWidth / width, areaHeight / height);
5 | case 'contain':
6 | return Math.min(areaWidth / width, areaHeight / height);
7 | case 'width':
8 | return areaWidth / width;
9 | case 'height':
10 | return areaHeight / height;
11 | default: break;
12 | }
13 | return 1;
14 | }
15 |
16 | export default function size(rect, areaWidth, areaHeight, method = 'cover', autoCenter = true) {
17 | const scale = getScale(method, rect.width, rect.height, areaWidth, areaHeight);
18 | const width = Math.ceil(rect.width * scale);
19 | const height = Math.ceil(rect.height * scale);
20 |
21 | let x = 0, y = 0;
22 |
23 | if (autoCenter) {
24 | x = Math.round((areaWidth - width) * 0.5);
25 | y = Math.round((areaHeight - height) * 0.5);
26 | }
27 |
28 | return {
29 | x,
30 | y,
31 | width,
32 | height,
33 | scale
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/src/math/smerp.js:
--------------------------------------------------------------------------------
1 | import smoothstep from './smoothstep';
2 |
3 | export default function smerp(from, to, startTime, endTime, time) {
4 | return from + (to - from) * smoothstep(startTime, endTime, time);
5 | }
6 |
--------------------------------------------------------------------------------
/src/math/smoothstep.js:
--------------------------------------------------------------------------------
1 | import clamp from './clamp';
2 |
3 | export default function smoothstep(min, max, value) {
4 | const x = clamp((value - min) / (max - min), 0, 1);
5 | return x * x * (3 - 2 * x);
6 | }
7 |
--------------------------------------------------------------------------------
/src/math/split-value-and-unit.js:
--------------------------------------------------------------------------------
1 | export default function splitValueAndUnit(prop) {
2 | const re = /(^-?\d*\.?\d*)(.*)/;
3 | const match = prop.match(re);
4 | const value = Number(match[1]);
5 | const unit = match[2];
6 | return {value, unit};
7 | }
8 |
--------------------------------------------------------------------------------
/src/math/weighted-average.js:
--------------------------------------------------------------------------------
1 | export default function weightedAverage(from, to, weight = 10) {
2 | return ((from * (weight - 1)) + to) / weight;
3 | }
4 |
--------------------------------------------------------------------------------
/src/math/weighted-distribution.js:
--------------------------------------------------------------------------------
1 | import random from './random';
2 |
3 | // greater probability of being halfway betweeen min and max
4 |
5 | export default function weightedDistribution(min, max, weight = 5) {
6 | let total = 0;
7 | for (let i = 0; i < weight; i++) {
8 | total += random(min, max);
9 | }
10 | return total / weight;
11 | }
12 |
--------------------------------------------------------------------------------
/src/media/can-play.js:
--------------------------------------------------------------------------------
1 | const el = typeof document !== 'undefined' && document.createElement('video');
2 |
3 | const tests = [
4 | {type: 'ogv', codec: 'video/ogg; codecs="theora"'},
5 | {type: 'mp4', codec: 'video/mp4; codecs="avc1.42E01E"'}, // H.264 Constrained baseline profile level 3
6 | {type: 'webm', codec: 'video/webm; codecs="vp8, vorbis"'},
7 | {type: 'vp9', codec: 'video/webm; codecs="vp9"'},
8 | {type: 'hls', codec: 'application/x-mpegURL; codecs="avc1.42E01E"'},
9 |
10 | {type: 'ogg', codec: 'audio/ogg; codecs="vorbis"'},
11 | {type: 'mp3', codec: 'audio/mpeg;'},
12 | {type: 'opus', codec: 'audio/ogg; codecs="opus"'},
13 | {type: 'wav', codec: 'audio/wav; codecs="1"'}
14 | ];
15 |
16 | export default tests.reduce((map, test) => {
17 | map[test.type] = !!(el && el.canPlayType && el.canPlayType(test.codec));
18 | return map;
19 | }, {});
20 |
--------------------------------------------------------------------------------
/src/media/cuepoints-reader.js:
--------------------------------------------------------------------------------
1 | export default function cuepointsReader() {
2 | const list = [];
3 | let reader = null;
4 | let dispatch;
5 | let currentPosition = 0;
6 | let lastPosition = -1;
7 | let tolerance = 0.2;
8 |
9 | function add(position, name, data) {
10 | list.push({position, name, data});
11 |
12 | list.sort((a, b) => a.position - b.position);
13 |
14 | return reader;
15 | }
16 |
17 | function onCuepoint(fn, thisArg) {
18 | if (fn) {
19 | dispatch = thisArg ? fn.bind(thisArg) : fn;
20 | } else {
21 | dispatch = null;
22 | }
23 | return reader;
24 | }
25 |
26 | function reset() {
27 | currentPosition = 0;
28 | lastPosition = -1;
29 | return reader;
30 | }
31 |
32 | function removeAll() {
33 | list.length = 0;
34 | return reset();
35 | }
36 |
37 | function setTolerance(value) {
38 | tolerance = value;
39 | return reader;
40 | }
41 |
42 | function inRange(cuepointPos, currentPos, lastPos) {
43 | if (cuepointPos > currentPos) {
44 | return false;
45 | }
46 | if (cuepointPos <= lastPos) {
47 | return false;
48 | }
49 |
50 | let diff = cuepointPos - currentPos;
51 | if (diff < 0) {
52 | diff = -diff;
53 | }
54 | return diff <= tolerance;
55 | }
56 |
57 | function check(currentPos, lastPos) {
58 | if (currentPos <= lastPos) {
59 | return;
60 | }
61 | if (typeof dispatch !== 'function') {
62 | return;
63 | }
64 |
65 | list.some(item => {
66 | if (inRange(item.position, currentPos, lastPos)) {
67 | dispatch(item);
68 | return true;
69 | }
70 | return false;
71 | });
72 | }
73 |
74 | function update(position) {
75 | currentPosition = position;
76 | check(currentPosition, lastPosition);
77 | lastPosition = currentPosition;
78 | return reader;
79 | }
80 |
81 | reader = Object.freeze({
82 | add,
83 | onCuepoint,
84 | removeAll,
85 | reset,
86 | setTolerance,
87 | update
88 | });
89 |
90 | return reader;
91 | }
92 |
--------------------------------------------------------------------------------
/src/media/index.js:
--------------------------------------------------------------------------------
1 | import canPlay from './can-play';
2 | import cuepointsReader from './cuepoints-reader';
3 | import iOSPlayVideoInline from './ios-play-video-inline';
4 | import videoPlayer from './video-player';
5 | import vimeo from './vimeo';
6 | import youtube from './youtube';
7 | import youtubeBasic from './youtube-basic';
8 |
9 | export default {
10 | canPlay,
11 | cuepointsReader,
12 | iOSPlayVideoInline,
13 | videoPlayer,
14 | vimeo,
15 | youtube,
16 | youtubeBasic
17 | };
18 |
--------------------------------------------------------------------------------
/src/media/ios-play-video-inline.js:
--------------------------------------------------------------------------------
1 | export default function iOSPlayVideoInline(el, loop = true) {
2 | const frameTime = 1 / 25;
3 |
4 | let self = null;
5 | let lastTime = 0;
6 | let playing = false;
7 |
8 | // This can (and should) be put in a css file instead of doing styleSheets[0].insertRule:
9 | const cssRule = '.iOSPlayVideoInline::-webkit-media-controls { display:none !important; }';
10 | document.styleSheets[0].insertRule(cssRule, 0);
11 |
12 | el.removeAttribute('controls');
13 | el.classList.add('iOSPlayVideoInline');
14 |
15 |
16 | function seek(time) {
17 | el.currentTime = time;
18 | return self;
19 | }
20 |
21 | function pause() {
22 | playing = false;
23 | return self;
24 | }
25 |
26 | function updateFrame() {
27 | if (!playing) {
28 | return;
29 | }
30 |
31 | window.requestAnimationFrame(updateFrame);
32 |
33 | const now = Date.now();
34 | const deltaTime = now - lastTime;
35 |
36 | if (deltaTime >= frameTime * 1000) {
37 | lastTime = now;
38 |
39 | const ended = el.currentTime + frameTime >= el.duration;
40 |
41 | if (ended && loop) {
42 | seek(0);
43 | } else if (ended) {
44 | pause();
45 | // self.emit('ended');
46 | } else {
47 | seek(el.currentTime + frameTime);
48 | }
49 |
50 | // self.emit('timeupdate', el.currentTime, self);
51 | }
52 | }
53 |
54 | function play() {
55 | playing = true;
56 | updateFrame();
57 | return self;
58 | }
59 |
60 | function destroy() {
61 | // self.removeAllListeners();
62 | pause();
63 | window.cancelAnimationFrame(updateFrame);
64 |
65 | return self;
66 | }
67 |
68 | self = Object.create(null, {
69 | destroy: {
70 | value: destroy
71 | },
72 | pause: {
73 | value: pause
74 | },
75 | play: {
76 | value: play
77 | },
78 | seek: {
79 | value: seek
80 | },
81 | el: {
82 | get: function() {
83 | return el;
84 | }
85 | },
86 | currentTime: {
87 | get: function() {
88 | return el.currentTime;
89 | }
90 | },
91 | duration: {
92 | get: function() {
93 | return el.duration;
94 | }
95 | },
96 | loop: {
97 | get: function() {
98 | return loop;
99 | },
100 | set: function(value) {
101 | loop = value;
102 | }
103 | },
104 | playing: {
105 | get: function() {
106 | return playing;
107 | }
108 | }
109 | });
110 |
111 | return Object.freeze(self);
112 | }
113 |
--------------------------------------------------------------------------------
/src/media/video-player.js:
--------------------------------------------------------------------------------
1 | import Emitter from '../events/emitter';
2 |
3 | export default function videoPlayer(videoEl) {
4 | let el = videoEl || document.createElement('video');
5 | let player = null;
6 |
7 | function metadataHandler() {
8 | player.emit('metadata', {
9 | src: el.currentSrc,
10 | width: el.videoWidth,
11 | height: el.videoHeight,
12 | duration: el.duration
13 | });
14 | }
15 |
16 | function canplayHandler() {
17 | player.emit('ready');
18 | }
19 |
20 | function playHandler() {
21 | player.emit('play');
22 | }
23 |
24 | function endedHandler() {
25 | player.emit('ended');
26 | }
27 |
28 | function errorHandler() {
29 | player.emit('error', el.error);
30 | }
31 |
32 | function timeupdateHandler() {
33 | player.emit('timeupdate', el.currentTime);
34 | }
35 |
36 | function removeEventListeners() {
37 | el.removeEventListener('loadedmetadata', metadataHandler);
38 | el.removeEventListener('canplaythrough', canplayHandler);
39 | el.removeEventListener('play', playHandler);
40 | el.removeEventListener('playing', playHandler);
41 | el.removeEventListener('error', errorHandler);
42 | el.removeEventListener('ended', endedHandler);
43 | el.removeEventListener('timeupdate', timeupdateHandler);
44 | }
45 |
46 | function addEventListeners() {
47 | removeEventListeners();
48 |
49 | el.addEventListener('loadedmetadata', metadataHandler, false);
50 | el.addEventListener('canplaythrough', canplayHandler, false);
51 | el.addEventListener('play', playHandler, false);
52 | el.addEventListener('playing', playHandler, false);
53 | el.addEventListener('error', errorHandler, false);
54 | el.addEventListener('ended', endedHandler, false);
55 | el.addEventListener('timeupdate', timeupdateHandler, false);
56 | }
57 |
58 | function destroy() {
59 | player.off();
60 | el.pause();
61 |
62 | try {
63 | el.removeAttribute('src');
64 | } catch (e) {}
65 |
66 | removeEventListeners();
67 |
68 | if (el.parentElement) {
69 | el.parentElement.removeChild(el);
70 | }
71 |
72 | el = null;
73 |
74 | return player;
75 | }
76 |
77 | function getBlobURL(url) {
78 | url = window.URL.createObjectURL(url);
79 | function revoke() {
80 | el.removeEventListener('canplaythrough', revoke);
81 | window.URL.revokeObjectURL(url);
82 | }
83 | el.addEventListener('canplaythrough', revoke);
84 | return url;
85 | }
86 |
87 | function load(url) {
88 | if (window.Blob && url instanceof window.Blob) {
89 | url = getBlobURL(url);
90 | }
91 | addEventListeners();
92 |
93 | el.crossOrigin = 'anonymous';
94 | el.preload = 'auto';
95 | el.src = url;
96 | el.load();
97 |
98 | return player;
99 | }
100 |
101 | function play() {
102 | el.play();
103 |
104 | return player;
105 | }
106 |
107 | function pause() {
108 | el.pause();
109 |
110 | return player;
111 | }
112 |
113 | function seek(time) {
114 | try {
115 | el.currentTime = time;
116 | } catch (e) {}
117 |
118 | return player;
119 | }
120 |
121 | addEventListeners();
122 |
123 | player = Object.assign(new Emitter(), {
124 | destroy,
125 | load,
126 | pause,
127 | play,
128 | seek
129 | });
130 |
131 | Object.defineProperties(player, {
132 | el: {
133 | get() {
134 | return el;
135 | }
136 | },
137 | currentTime: {
138 | get() {
139 | return el.currentTime;
140 | },
141 | set(value) {
142 | el.currentTime = value;
143 | }
144 | },
145 | duration: {
146 | get() {
147 | return el.duration;
148 | }
149 | },
150 | volume: {
151 | get() {
152 | return el.volume;
153 | },
154 | set(value) {
155 | el.volume = value;
156 | }
157 | }
158 | });
159 |
160 | return player;
161 | }
162 |
--------------------------------------------------------------------------------
/src/media/vimeo.js:
--------------------------------------------------------------------------------
1 | import Emitter from '../events/emitter';
2 |
3 | // https://developer.vimeo.com/player/js-api
4 |
5 | export default function vimeo(el) {
6 | const vimeoPlayer = el.contentWindow;
7 | const re = /^https?:\/\/player.vimeo.com/;
8 | let player = null;
9 | let origin = '*';
10 | let paused = false;
11 |
12 | function sendCommand(method, value = '') {
13 | const data = {
14 | method
15 | };
16 |
17 | if (value) {
18 | data.value = value;
19 | }
20 |
21 | const message = JSON.stringify(data);
22 | vimeoPlayer.postMessage(message, origin);
23 | }
24 |
25 | function play() {
26 | paused = false;
27 | sendCommand('play');
28 | }
29 |
30 | function pause() {
31 | paused = true;
32 | sendCommand('pause');
33 | }
34 |
35 | function onReady() {
36 | sendCommand('addEventListener', 'play');
37 | sendCommand('addEventListener', 'pause');
38 | sendCommand('addEventListener', 'finish');
39 | sendCommand('addEventListener', 'playProgress');
40 | player.emit('ready');
41 | }
42 |
43 | function onPlay() {
44 | paused = false;
45 | player.emit('play');
46 | }
47 |
48 | function onPause() {
49 | paused = true;
50 | player.emit('pause');
51 | }
52 |
53 | function onFinish() {
54 | player.emit('ended');
55 | }
56 |
57 | function onPlayProgress(data) {
58 | player.emit('timeupdate', data.seconds);
59 | }
60 |
61 | function onMessage(event) {
62 | const isVimeo = re.test(event.origin);
63 |
64 | if (!isVimeo) {
65 | return;
66 | }
67 |
68 | const data = JSON.parse(event.data);
69 |
70 | if (data.player_id && el.id !== data.player_id) {
71 | return;
72 | }
73 |
74 | if (origin === '*') {
75 | origin = event.origin;
76 | }
77 |
78 | switch (data.event) {
79 | case 'ready':
80 | onReady(data.player_id);
81 | break;
82 | case 'playProgress':
83 | onPlayProgress(data.data);
84 | break;
85 | case 'play':
86 | onPlay();
87 | break;
88 | case 'pause':
89 | onPause();
90 | break;
91 | case 'finish':
92 | onFinish();
93 | break;
94 | default:
95 | break;
96 | }
97 | }
98 |
99 | function destroy() {
100 | window.removeEventListener('message', onMessage);
101 | }
102 |
103 | window.addEventListener('message', onMessage);
104 |
105 | player = Object.assign(new Emitter(), {
106 | play,
107 | pause,
108 | paused: () => paused,
109 | destroy
110 | });
111 |
112 | return player;
113 | }
114 |
--------------------------------------------------------------------------------
/src/media/youtube-basic.js:
--------------------------------------------------------------------------------
1 | export default function youtubeBasic(el) {
2 | const iframe = el.contentWindow;
3 |
4 | function sendCommand(command) {
5 | iframe.postMessage(`{"event":"command","func":"${command}","args":""}`, '*');
6 | }
7 |
8 | function play() {
9 | sendCommand('playVideo');
10 | }
11 |
12 | function pause() {
13 | sendCommand('pauseVideo');
14 | }
15 |
16 | return {
17 | play,
18 | pause
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/media/youtube.js:
--------------------------------------------------------------------------------
1 | // https://developers.google.com/youtube/iframe_api_reference#Events
2 | import EventEmitter from 'eventemitter3';
3 |
4 | export const getYouTubeId = url => {
5 | const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
6 | const match = url.match(regExp);
7 | if (match && match[2].length === 11) {
8 | return match[2];
9 | }
10 | return null;
11 | };
12 |
13 | export default function youtube(el) {
14 | let emitter = null;
15 | let player = null;
16 | let paused = false;
17 |
18 | function play() {
19 | paused = false;
20 | player.playVideo();
21 | return emitter;
22 | }
23 |
24 | function pause() {
25 | paused = true;
26 | player.pauseVideo();
27 | return emitter;
28 | }
29 |
30 | function onReady() {
31 | emitter.emit('ready');
32 | }
33 |
34 | function onStateChange(event) {
35 | const {PlayerState} = window.YT;
36 |
37 | switch (event.data) {
38 | case PlayerState.CUED:
39 | case PlayerState.BUFFERING:
40 | break;
41 | case PlayerState.PLAYING:
42 | paused = false;
43 | emitter.emit('play');
44 | break;
45 | case PlayerState.PAUSED:
46 | paused = true;
47 | emitter.emit('pause');
48 | break;
49 | case PlayerState.ENDED:
50 | emitter.emit('ended');
51 | break;
52 | default: break;
53 | }
54 | }
55 |
56 | function destroy() {}
57 |
58 | function createPlayer() {
59 | if (player) {
60 | return;
61 | }
62 |
63 | player = new window.YT.Player(el, {
64 | events: {
65 | onReady,
66 | onStateChange
67 | }
68 | });
69 | }
70 |
71 |
72 |
73 | if (window.YT) {
74 | createPlayer();
75 | } else if (window.ytPlayerCalls) {
76 | window.ytPlayerCalls.push(createPlayer);
77 | } else {
78 | window.ytPlayerCalls = [createPlayer];
79 | window.onYouTubeIframeAPIReady = function() {
80 | window.ytPlayerCalls.forEach((call) => call());
81 | };
82 | const script = document.createElement('script');
83 | script.src = 'https://www.youtube.com/iframe_api';
84 | document.body.appendChild(script);
85 | }
86 |
87 | emitter = Object.assign(new EventEmitter(), {
88 | play,
89 | pause,
90 | paused: () => paused,
91 | destroy
92 | });
93 |
94 | return emitter;
95 | }
96 |
--------------------------------------------------------------------------------
/src/object-pool/index.js:
--------------------------------------------------------------------------------
1 | export default function objectPool(factoryFn) {
2 |
3 | let pool = [];
4 | let numCreated = 0;
5 |
6 | return {
7 | getPool () {
8 | return pool;
9 | },
10 | get () {
11 | if ( pool.length > 0 ) {
12 | return pool.pop();
13 | } else {
14 | numCreated++;
15 | return factoryFn();
16 | }
17 | },
18 | dispose (instance) {
19 | pool.push(instance);
20 | },
21 | fill (count) {
22 | while ( pool.length < count ) {
23 | numCreated++;
24 | pool[pool.length] = factoryFn();
25 | }
26 | },
27 | empty () {
28 | pool = [];
29 | },
30 | getNumCreated() {
31 | return numCreated;
32 | }
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/object/clone.js:
--------------------------------------------------------------------------------
1 | export default function clone(ob) {
2 | return JSON.parse(JSON.stringify(ob));
3 | }
4 |
--------------------------------------------------------------------------------
/src/object/filter.js:
--------------------------------------------------------------------------------
1 | export default function filter(ob, predicate) {
2 | return Object.keys(ob)
3 | .filter(key => predicate(key, ob[key]))
4 | .reduce((newOb, key) => {
5 | newOb[key] = ob[key];
6 | return newOb;
7 | }, {});
8 | }
9 |
--------------------------------------------------------------------------------
/src/object/index.js:
--------------------------------------------------------------------------------
1 | import clone from './clone';
2 | import filter from './filter';
3 | import map from './map';
4 |
5 | export default {
6 | clone,
7 | filter,
8 | map
9 | };
10 |
--------------------------------------------------------------------------------
/src/object/map.js:
--------------------------------------------------------------------------------
1 | export default function map(ob, fn) {
2 | return Object.keys(ob)
3 | .reduce((newOb, key) => {
4 | newOb[key] = fn(key, ob[key]);
5 | return newOb;
6 | }, {});
7 | }
8 |
--------------------------------------------------------------------------------
/src/particle/particle-group.js:
--------------------------------------------------------------------------------
1 | import linkedList from '../linked-list';
2 | import objectPool from '../object-pool';
3 | import Particle from './index';
4 |
5 | export default class ParticleGroup {
6 | constructor(factoryFn) {
7 | if (!factoryFn) {
8 | factoryFn = () => new Particle();
9 | }
10 | this.update = this.update.bind(this);
11 | this.list = linkedList();
12 | this.pool = objectPool(factoryFn);
13 | }
14 |
15 | create(options) {
16 | const p = this.pool.get();
17 | p.reset(options);
18 | this.list.add(p);
19 | return p;
20 | }
21 |
22 | add(p) {
23 | this.list.add(p);
24 | }
25 |
26 | remove(p) {
27 | this.list.remove(p);
28 | this.pool.dispose(p);
29 | }
30 |
31 | forEach(fn) {
32 | let p = this.list.first;
33 | while (p) {
34 | fn(p);
35 | p = p.next;
36 | }
37 | }
38 |
39 | update(fn) {
40 | let p = this.list.first;
41 | while (p) {
42 | const next = p.next;
43 | p.update();
44 | if (typeof fn === 'function') {
45 | fn(p);
46 | }
47 | if (!p.alive) {
48 | this.remove(p);
49 | }
50 | p = next;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/platform/android-native.js:
--------------------------------------------------------------------------------
1 | import android from './android';
2 |
3 | //http://stackoverflow.com/questions/14403766/how-to-detect-the-stock-android-browser
4 | export default function androidNative(ua = (typeof navigator !== 'undefined' && navigator.userAgent)) {
5 | if (!android(ua)) {
6 | return false;
7 | }
8 |
9 | const isAndroidMobile = ua.indexOf('Mozilla/5.0') > -1 && ua.indexOf('AppleWebKit') > -1;
10 |
11 | const reAppleWebKit = /AppleWebKit\/([\d.]+)/;
12 | const resultAppleWebKit = reAppleWebKit.exec(ua);
13 | const appleWebKitVersion = resultAppleWebKit ? parseFloat(reAppleWebKit.exec(ua)[1]) : null;
14 |
15 | const reChrome = /Chrome\/([\d.]+)/;
16 | const resultChrome = reChrome.exec(ua);
17 | const chromeVersion = resultChrome ? parseFloat(reChrome.exec(ua)[1]) : null;
18 |
19 | return isAndroidMobile && (appleWebKitVersion && appleWebKitVersion < 537) || (chromeVersion && chromeVersion < 37);
20 | }
21 |
--------------------------------------------------------------------------------
/src/platform/android-version.js:
--------------------------------------------------------------------------------
1 | import android from './android';
2 |
3 | export default function androidVersion(ua = (typeof navigator !== 'undefined' && navigator.userAgent)) {
4 | if (!android(ua)) {
5 | return 0;
6 | }
7 | const version = ua.match(/Android (\d+(?:\.\d+)+);/)[1];
8 | const [a, b] = version.split('.');
9 | return parseFloat(`${a}.${b}`);
10 | }
11 |
--------------------------------------------------------------------------------
/src/platform/android.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /Android/i.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/chrome-ios.js:
--------------------------------------------------------------------------------
1 | import ios from './ios';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => ios(ua) && /CriOS/.test(ua);
4 |
--------------------------------------------------------------------------------
/src/platform/desktop.js:
--------------------------------------------------------------------------------
1 | import mobile from './mobile';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => !mobile(ua);
4 |
--------------------------------------------------------------------------------
/src/platform/device-orientation.js:
--------------------------------------------------------------------------------
1 | export default () => !!(typeof window !== 'undefined' && window.DeviceOrientationEvent);
2 |
--------------------------------------------------------------------------------
/src/platform/firefox.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /Firefox/.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/ie-version.js:
--------------------------------------------------------------------------------
1 | export default function ieVersion(ua = (typeof navigator !== 'undefined' && navigator.userAgent)) {
2 | let v = 0;
3 | if (/MSIE (\d+\.\d+);/.test(ua)) {
4 | v = parseInt(RegExp.$1, 10);
5 | } else if (/Trident\/(\d+\.\d+)(.*)rv:(\d+\.\d+)/.test(ua)) {
6 | v = parseInt(RegExp.$3, 10);
7 | }
8 | return v;
9 | }
10 |
--------------------------------------------------------------------------------
/src/platform/ie.js:
--------------------------------------------------------------------------------
1 | import ieVersion from './ie-version';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => ieVersion(ua) > 0;
4 |
--------------------------------------------------------------------------------
/src/platform/index.js:
--------------------------------------------------------------------------------
1 | import android from './android';
2 | import androidNative from './android-native';
3 | import androidVersion from './android-version';
4 | import chromeIOS from './chrome-ios';
5 | import desktop from './desktop';
6 | import deviceOrientation from './device-orientation';
7 | import firefox from './firefox';
8 | import ie from './ie';
9 | import ieVersion from './ie-version';
10 | import ios from './ios';
11 | import iosVersion from './ios-version';
12 | import ipad from './ipad';
13 | import iphone from './iphone';
14 | import language from './language';
15 | import linux from './linux';
16 | import localHost from './local-host';
17 | import mac from './mac';
18 | import mobile from './mobile';
19 | import mp4 from './mp4';
20 | import safari from './safari';
21 | import safariIOS from './safari-ios';
22 | import screen from './screen';
23 | import webgl from './webgl';
24 | import webm from './webm';
25 | import windows from './windows';
26 | import windowsPhone from './windows-phone';
27 |
28 | export default {
29 | android: android(),
30 | androidNative: androidNative(),
31 | androidVersion: androidVersion(),
32 | chromeIOS: chromeIOS(),
33 | desktop: desktop(),
34 | deviceOrientation: deviceOrientation(),
35 | firefox: firefox(),
36 | ie: ie(),
37 | ieVersion: ieVersion(),
38 | ios: ios(),
39 | iosVersion: iosVersion(),
40 | ipad: ipad(),
41 | iphone: iphone(),
42 | language: language(),
43 | linux: linux(),
44 | localHost: localHost(),
45 | mac: mac(),
46 | mobile: mobile(),
47 | mp4: mp4(),
48 | safari: safari(),
49 | safariIOS: safariIOS(),
50 | screen: screen,
51 | webgl: webgl(),
52 | webm: webm(),
53 | windows: windows(),
54 | windowsPhone: windowsPhone()
55 | };
56 |
--------------------------------------------------------------------------------
/src/platform/ios-version.js:
--------------------------------------------------------------------------------
1 | import ios from './ios';
2 |
3 | export default function iosVersion(ua = (typeof navigator !== 'undefined' && navigator.userAgent)) {
4 | if (ios(ua)) {
5 | const [, b, c] = ua.match(/OS (\d+)_(\d+)/i);
6 | if (b && c) {
7 | return parseFloat(`${b}.${c}`);
8 | }
9 | }
10 | return 0;
11 | }
12 |
--------------------------------------------------------------------------------
/src/platform/ios.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /iP[ao]d|iPhone/i.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/ipad.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /iPad/i.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/iphone.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /iPod|iPhone/i.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/language.js:
--------------------------------------------------------------------------------
1 | export default function language() {
2 | if (typeof navigator === 'undefined') {
3 | return null;
4 | }
5 | return (navigator.languages && navigator.languages[0]) || (navigator.language || navigator.userLanguage);
6 | }
7 |
--------------------------------------------------------------------------------
/src/platform/linux.js:
--------------------------------------------------------------------------------
1 | import android from './android';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => !android(ua) && /Linux/.test(ua);
4 |
--------------------------------------------------------------------------------
/src/platform/local-host.js:
--------------------------------------------------------------------------------
1 | export default (href = (typeof window !== 'undefined' && window.location.href)) => (
2 | /^(?:https?:\/\/)?(?:localhost|192\.168)/.test(href)
3 | );
4 |
--------------------------------------------------------------------------------
/src/platform/mac.js:
--------------------------------------------------------------------------------
1 | import ios from './ios';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => !ios(ua) && /Mac OS/.test(ua);
4 |
--------------------------------------------------------------------------------
/src/platform/mobile.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => {
2 | return /Android|webOS|iPhone|iP[ao]d|BlackBerry|IEMobile|Opera Mini|Windows Phone|SymbianOS/i.test(ua);
3 | };
4 |
--------------------------------------------------------------------------------
/src/platform/mp4.js:
--------------------------------------------------------------------------------
1 | export default () => {
2 | const el = typeof document !== 'undefined' && document.createElement('video');
3 | return !!(el && el.canPlayType && el.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'));
4 | };
5 |
--------------------------------------------------------------------------------
/src/platform/safari-ios.js:
--------------------------------------------------------------------------------
1 | import ios from './ios';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => ios(ua) && /AppleWebKit/.test(ua);
4 |
--------------------------------------------------------------------------------
/src/platform/safari.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => (
2 | !/Android/.test(ua) && !/Chrome/.test(ua) && /Safari/.test(ua)
3 | );
4 |
--------------------------------------------------------------------------------
/src/platform/screen.js:
--------------------------------------------------------------------------------
1 | const hasWin = typeof window !== 'undefined';
2 |
3 | export default {
4 | width: hasWin ? Math.max(window.outerWidth, window.screen.width) : 0,
5 | height: hasWin ? Math.max(window.outerHeight, window.screen.height) : 0,
6 | dpr: hasWin ? window.devicePixelRatio || 1 : 1,
7 | retina: hasWin ? window.devicePixelRatio > 1 : false
8 | };
9 |
--------------------------------------------------------------------------------
/src/platform/webgl.js:
--------------------------------------------------------------------------------
1 | export default function webgl() {
2 | try {
3 | const canvas = document.createElement('canvas');
4 | const context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
5 | return !!(window.WebGLRenderingContext && context);
6 | } catch (e) {
7 | return false;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/platform/webm.js:
--------------------------------------------------------------------------------
1 | export default () => {
2 | const el = typeof document !== 'undefined' && document.createElement('video');
3 | return !!(el && el.canPlayType && el.canPlayType('video/webm; codecs="vp8, vorbis"'));
4 | };
5 |
--------------------------------------------------------------------------------
/src/platform/windows-phone.js:
--------------------------------------------------------------------------------
1 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => /Windows Phone/i.test(ua);
2 |
--------------------------------------------------------------------------------
/src/platform/windows.js:
--------------------------------------------------------------------------------
1 | import windowsPhone from './windows-phone';
2 |
3 | export default (ua = (typeof navigator !== 'undefined' && navigator.userAgent)) => (
4 | !windowsPhone(ua) && /Windows/.test(ua)
5 | );
6 |
--------------------------------------------------------------------------------
/src/polyfill/class-list.js:
--------------------------------------------------------------------------------
1 | /*
2 | * classList (partial polyfill for IE 10, IE 11 and Firefox <24)
3 | * adapted from: https://github.com/eligrey/classList.js/blob/master/classList.js
4 | */
5 |
6 | (function() {
7 |
8 | if (typeof document === 'undefined') {
9 | return;
10 | }
11 |
12 | let testElement = document.createElement('_');
13 |
14 | testElement.classList.add('c1', 'c2');
15 |
16 | // Polyfill for IE 10/11 and Firefox <26, where classList.add and
17 | // classList.remove exist but support only one argument at a time.
18 | if (!testElement.classList.contains('c2')) {
19 | function createMethod(method) {
20 | const original = window.DOMTokenList.prototype[method];
21 |
22 | window.DOMTokenList.prototype[method] = function(token) {
23 | let i;
24 | const len = arguments.length;
25 |
26 | for (i = 0; i < len; i++) {
27 | token = arguments[i];
28 | original.call(this, token);
29 | }
30 | };
31 | }
32 | createMethod('add');
33 | createMethod('remove');
34 | }
35 |
36 | testElement.classList.toggle('c3', false);
37 |
38 | // Polyfill for IE 10, IE 11 and Firefox <24, where classList.toggle does not
39 | // support the second argument.
40 | if (testElement.classList.contains('c3')) {
41 | const toggle = window.DOMTokenList.prototype.toggle;
42 |
43 | window.DOMTokenList.prototype.toggle = function(token, force) {
44 | force = !!force;
45 | if (arguments.length > 1 && this.contains(token) === force) {
46 | return force;
47 | } else {
48 | return toggle.call(this, token);
49 | }
50 | };
51 | }
52 |
53 | testElement = null;
54 | }());
55 |
--------------------------------------------------------------------------------
/src/polyfill/console.js:
--------------------------------------------------------------------------------
1 | (function(fn) {
2 | if (typeof window === 'undefined') {
3 | return;
4 | }
5 |
6 | window.console = window.console || {};
7 | const methods = [
8 | 'assert',
9 | 'clear',
10 | 'count',
11 | 'debug',
12 | 'dir',
13 | 'dirxml',
14 | 'error',
15 | 'group',
16 | 'groupCollapsed',
17 | 'groupEnd',
18 | 'info',
19 | 'log',
20 | 'markTimeline',
21 | 'memory',
22 | 'profile',
23 | 'profileEnd',
24 | 'table',
25 | 'time',
26 | 'timeEnd',
27 | 'timeStamp',
28 | 'timeline',
29 | 'timelineEnd',
30 | 'trace',
31 | 'warn'
32 | ];
33 | methods.forEach((name) => {
34 | window.console[name] = window.console[name] || fn;
35 | });
36 | }(function() {}));
37 |
--------------------------------------------------------------------------------
/src/polyfill/index.js:
--------------------------------------------------------------------------------
1 | import './class-list';
2 | import './console';
3 | import './request-animation-frame';
4 |
--------------------------------------------------------------------------------
/src/polyfill/request-animation-frame.js:
--------------------------------------------------------------------------------
1 | /*
2 | * requestAnimationFrame (ios6 and android < 4.4)
3 | */
4 | (function() {
5 | if (typeof window === 'undefined') {
6 | return;
7 | }
8 | if (!window.requestAnimationFrame) {
9 | const vendors = ['ms', 'moz', 'webkit', 'o'];
10 | for (let x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
11 | window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
12 | window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] +
13 | 'CancelRequestAnimationFrame'];
14 | }
15 | }
16 | }());
17 |
--------------------------------------------------------------------------------
/src/popup/index.js:
--------------------------------------------------------------------------------
1 | export default function popup(url, width = 800, height = 600, name = '') {
2 | const left = (window.screen.width - width) / 2;
3 | const top = (window.screen.height - height) / 2;
4 | // const left = (window.screen.availWidth - width) / 2;
5 | // const top = (window.screen.availHeight - height) / 2;
6 | const defaults = 'directories=no,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no';
7 | const params = `width=${width},height=${height},top=${top},left=${left},${defaults}`;
8 | const win = window.open(url, name, params);
9 | if (win === null || typeof win === 'undefined') {
10 | return false;
11 | }
12 | if (window.focus) {
13 | win.focus();
14 | }
15 | return true;
16 | }
17 |
--------------------------------------------------------------------------------
/src/quad-tree/index.js:
--------------------------------------------------------------------------------
1 |
2 | class Node {
3 | constructor(bounds, depth, maxDepth, maxChildren) {
4 | this._bounds = bounds;
5 | this._depth = depth;
6 | this._maxDepth = maxDepth;
7 | this._maxChildren = maxChildren;
8 |
9 | this.children = [];
10 | this.nodes = [];
11 | }
12 |
13 | insert(item) {
14 | if (this.nodes.length) {
15 | const index = this._findIndex(item);
16 | this.nodes[index].insert(item);
17 | return;
18 | }
19 |
20 | this.children.push(item);
21 |
22 | if (!(this._depth >= this._maxDepth) && this.children.length > this._maxChildren) {
23 |
24 | this.subdivide();
25 |
26 | for (let i = 0; i < this.children.length; i++) {
27 | this.insert(this.children[i]);
28 | }
29 |
30 | this.children.length = 0;
31 | }
32 | }
33 |
34 | retrieve(item) {
35 | if (this.nodes.length) {
36 | const index = this._findIndex(item);
37 | return this.nodes[index].retrieve(item);
38 | }
39 |
40 | return this.children;
41 | }
42 |
43 | _findIndex(item) {
44 | const {x, y, width, height} = this._bounds;
45 |
46 | const right = item.x > x + width / 2;
47 | const bottom = item.y > y + height / 2;
48 |
49 | let index;
50 |
51 | if (right) {
52 | index = bottom ? Node.BR : Node.TR;
53 | } else {
54 | index = bottom ? Node.BL : Node.TL;
55 | }
56 |
57 | return index;
58 | }
59 |
60 | subdivide() {
61 | const depth = this._depth + 1;
62 |
63 | const {x, y, width, height} = this._bounds;
64 | const w = width / 2;
65 | const h = height / 2;
66 |
67 | this.nodes[Node.TL] = new Node({
68 | x,
69 | y,
70 | width: w,
71 | height: h
72 | },
73 | depth, this._maxDepth, this._maxChildren);
74 |
75 | this.nodes[Node.TR] = new Node({
76 | x: x + w,
77 | y,
78 | width: w,
79 | height: h
80 | },
81 | depth, this._maxDepth, this._maxChildren);
82 |
83 | this.nodes[Node.BL] = new Node({
84 | x,
85 | y: y + h,
86 | width: w,
87 | height: h
88 | },
89 | depth, this._maxDepth, this._maxChildren);
90 |
91 | this.nodes[Node.BR] = new Node({
92 | x: x + w,
93 | y: y + h,
94 | width: w,
95 | height: h
96 | },
97 | depth, this._maxDepth, this._maxChildren);
98 | }
99 |
100 | clear() {
101 | this.children.length = 0;
102 |
103 | while (this.nodes.length) {
104 | this.nodes.pop().clear();
105 | }
106 | }
107 | }
108 |
109 | Node.TL = 0;
110 | Node.TR = 1;
111 | Node.BL = 2;
112 | Node.BR = 3;
113 |
114 | export default class QuadTree {
115 | constructor(bounds, maxDepth = -1, maxChildren = -1) {
116 | this.root = new Node(bounds, 0, maxDepth, maxChildren);
117 | }
118 |
119 | insert(item) {
120 | if (Array.isArray(item)) {
121 | for (let i = 0; i < item.length; i++) {
122 | this.root.insert(item[i]);
123 | }
124 | } else {
125 | this.root.insert(item);
126 | }
127 | }
128 |
129 | clear() {
130 | this.root.clear();
131 | }
132 |
133 | retrieve(item) {
134 | return this.root.retrieve(item);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/share/email.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function email(url, subject = '', body = '') {
4 | url = encodeURIComponent(url);
5 | subject = encodeURIComponent(subject);
6 |
7 | const newlines = encodeURIComponent('\r\n\r\n');
8 | body = body ? `${encodeURIComponent(body)}${newlines}` : '';
9 |
10 | return popup(`mailto:?subject=${subject}&body=${body}${url}`);
11 | }
12 |
--------------------------------------------------------------------------------
/src/share/facebook.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function facebook(url) {
4 | url = encodeURIComponent(url);
5 | return popup(`https://www.facebook.com/sharer/sharer.php?u=${url}`);
6 | }
7 |
--------------------------------------------------------------------------------
/src/share/googleplus.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function googleplus(url) {
4 | url = encodeURIComponent(url);
5 | return popup(`https://plus.google.com/share?url=${url}`);
6 | }
7 |
--------------------------------------------------------------------------------
/src/share/index.js:
--------------------------------------------------------------------------------
1 | import email from './email';
2 | import facebook from './facebook';
3 | import googleplus from './googleplus';
4 | import linkedin from './linkedin';
5 | import pinterest from './pinterest';
6 | import reddit from './reddit';
7 | import renren from './renren';
8 | import sms from './sms';
9 | import twitter from './twitter';
10 | import vkontakte from './vkontakte';
11 | import weibo from './weibo';
12 | import whatsapp from './whatsapp';
13 |
14 | export default {
15 | email,
16 | facebook,
17 | googleplus,
18 | linkedin,
19 | pinterest,
20 | reddit,
21 | renren,
22 | sms,
23 | twitter,
24 | vkontakte,
25 | weibo,
26 | whatsapp
27 | };
28 |
--------------------------------------------------------------------------------
/src/share/linkedin.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function linkedin(url, title = '') {
4 | url = encodeURIComponent(url);
5 | title = encodeURIComponent(title);
6 | return popup(`https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${title}`);
7 | }
8 |
--------------------------------------------------------------------------------
/src/share/pinterest.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function pinterest(url, media, desc = '') {
4 | url = encodeURIComponent(url);
5 | media = encodeURIComponent(media);
6 | desc = encodeURIComponent(desc);
7 | return popup(`https://pinterest.com/pin/create/button/?url=${url}&media=${media}&description=${desc}`);
8 | }
9 |
--------------------------------------------------------------------------------
/src/share/reddit.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function reddit(url, title = '') {
4 | url = encodeURIComponent(url);
5 | title = encodeURIComponent(title);
6 | return popup(`https://www.reddit.com/submit?url=${url}&title=${title}`);
7 | }
8 |
--------------------------------------------------------------------------------
/src/share/renren.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function vkontakte(url, title = '') {
4 | url = encodeURIComponent(url);
5 | title = encodeURIComponent(title);
6 | return popup(`http://share.renren.com/share/buttonshare.do?link=${url}&title=${title}`);
7 | }
8 |
--------------------------------------------------------------------------------
/src/share/sms.js:
--------------------------------------------------------------------------------
1 | export default function sms(url, body = '') {
2 | url = encodeURIComponent(url);
3 |
4 | const newlines = encodeURIComponent('\r\n\r\n');
5 | body = body ? `${encodeURIComponent(body)}${newlines}` : '';
6 |
7 | const ios = /iP[ao]d|iPhone/i.test(navigator.userAgent);
8 | const delim = ios ? '&' : '?';
9 |
10 | window.location.href = `sms:${delim}body=${body}${url}`;
11 | }
12 |
--------------------------------------------------------------------------------
/src/share/twitter.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function twitter(url, text = '', hashtags = '', related = '') {
4 | url = encodeURIComponent(url);
5 | text = encodeURIComponent(text);
6 | hashtags = encodeURIComponent(hashtags);
7 | related = encodeURIComponent(related);
8 |
9 | return popup(`https://twitter.com/intent/tweet?url=${url}&text=${text}&hashtags=${hashtags}&related=${related}`);
10 | }
11 |
--------------------------------------------------------------------------------
/src/share/vkontakte.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function vkontakte(url, title = '', description = '', image = '') {
4 | url = encodeURIComponent(url);
5 | title = encodeURIComponent(title);
6 | description = encodeURIComponent(description);
7 | image = encodeURIComponent(image);
8 | return popup(`http://vkontakte.ru/share.php?url=${url}&title=${title}&description=${description}&image=${image}`);
9 | }
10 |
--------------------------------------------------------------------------------
/src/share/weibo.js:
--------------------------------------------------------------------------------
1 | import popup from '../popup';
2 |
3 | export default function weibo(url, title = '', image = '') {
4 | url = encodeURIComponent(url);
5 | title = encodeURIComponent(title);
6 | image = encodeURIComponent(image);
7 |
8 | const params = `url=${url}&appkey=&title=${title}&pic=${image}&ralateUid=&language=zh_cn`;
9 | return popup(`http://service.weibo.com/share/share.php?${params}`);
10 | }
11 |
--------------------------------------------------------------------------------
/src/share/whatsapp.js:
--------------------------------------------------------------------------------
1 | export default function whatsapp(url, body = '') {
2 | url = encodeURIComponent(url);
3 |
4 | const newlines = encodeURIComponent('\r\n\r\n');
5 | body = body ? `${encodeURIComponent(body)}${newlines}` : '';
6 |
7 | window.location.href = `whatsapp://send?text=${body}${url}`;
8 | }
9 |
--------------------------------------------------------------------------------
/src/storage/index.js:
--------------------------------------------------------------------------------
1 | function load(key) {
2 | let item = null;
3 |
4 | try {
5 | item = localStorage.getItem(key);
6 | } catch (err) {}
7 |
8 | return item;
9 | }
10 |
11 | function save(key, item) {
12 | try {
13 | localStorage.setItem(key, item);
14 | return true;
15 | } catch (err) {
16 | console.error('Couldn\'t save in localStorage');
17 | }
18 | return false;
19 | }
20 |
21 | function loadJSON(key) {
22 | const item = load(key);
23 | return item ? JSON.parse(item) : null;
24 | }
25 |
26 | function saveJSON(key, item) {
27 | return save(key, JSON.stringify(item));
28 | }
29 |
30 | function remove(key) {
31 | try {
32 | localStorage.removeItem(key);
33 | } catch (err) {}
34 | }
35 |
36 | export default {load, save, loadJSON, saveJSON, remove};
37 |
--------------------------------------------------------------------------------
/src/string/after-first.js:
--------------------------------------------------------------------------------
1 | // everything after the first occurrence of substr in str
2 | export default function afterFirst(str, substr) {
3 | let index = str.indexOf(substr);
4 | if (index === -1) {
5 | return '';
6 | }
7 | index += substr.length;
8 | return str.slice(index);
9 | }
10 |
--------------------------------------------------------------------------------
/src/string/after-last.js:
--------------------------------------------------------------------------------
1 | // everything after the last occurence of substr in str
2 | export default function afterLast(str, substr) {
3 | let index = str.lastIndexOf(substr);
4 | if (index === -1) {
5 | return '';
6 | }
7 | index += substr.length;
8 | return str.slice(index);
9 | }
10 |
--------------------------------------------------------------------------------
/src/string/before-first.js:
--------------------------------------------------------------------------------
1 | // everything before the first occurrence of substr in str
2 | export default function beforeFirst(str, substr) {
3 | const index = str.indexOf(substr);
4 | if (index === -1) {
5 | return '';
6 | }
7 | return str.slice(0, index);
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/before-last.js:
--------------------------------------------------------------------------------
1 | // everything before the last occurrence of substr in the string.
2 | export default function beforeLast(str, substr) {
3 | const index = str.lastIndexOf(substr);
4 | if (index === -1) {
5 | return '';
6 | }
7 | return str.slice(0, index);
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/begins-with.js:
--------------------------------------------------------------------------------
1 | // whether str begins with substr
2 | export default function beginsWith(str, substr) {
3 | return str.indexOf(substr) === 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/between.js:
--------------------------------------------------------------------------------
1 | // everything after the first occurance of start and before the first occurrence of end
2 | export default function between(str, start, end) {
3 | let substr = '';
4 | let startIndex = str.indexOf(start);
5 | if (startIndex !== -1) {
6 | startIndex += start.length;
7 | const endIndex = str.indexOf(end, startIndex);
8 | if (endIndex !== -1) {
9 | substr = str.slice(startIndex, endIndex);
10 | }
11 | }
12 | return substr;
13 | }
14 |
--------------------------------------------------------------------------------
/src/string/block.js:
--------------------------------------------------------------------------------
1 | import escapePattern from './escape-pattern';
2 | import truncate from './truncate';
3 | // Utility method that intelligently breaks up your string,
4 | // allowing you to create blocks of readable text.
5 | // This method returns you the closest possible match to the delim paramater,
6 | // while keeping the text length within the len paramter.
7 | // If a match can't be found in your specified length an '...' is added to that block,
8 | // and the blocking continues untill all the text is broken apart.
9 | export default function block(str, len, delim = '.') {
10 | const arr = [];
11 |
12 | if (!str || !str.includes(delim)) {
13 | return arr;
14 | }
15 |
16 | if (delim === ' ') {
17 | str += delim;
18 | }
19 |
20 | let chrIndex = 0;
21 | const replPatt = new RegExp('[^' + escapePattern(delim) + ']+$');
22 |
23 | while (chrIndex < str.length) {
24 | let subString = str.substr(chrIndex, len);
25 | if (!subString.includes(delim)) {
26 | arr.push(truncate(subString, subString.length));
27 | chrIndex += subString.length;
28 | }
29 | subString = subString.replace(replPatt, '');
30 | chrIndex += subString.length;
31 | arr.push(subString.trim());
32 | }
33 | return arr;
34 | }
35 |
--------------------------------------------------------------------------------
/src/string/capitalize.js:
--------------------------------------------------------------------------------
1 | // Capitalize the first word in a string or all words
2 | export default function capitalize(str, all = false) {
3 | const substr = str.trimLeft();
4 | const re = all ? /^.|\b./g : /(^\w)/;
5 | return substr.replace(re, (match) => match.toUpperCase());
6 | }
7 |
--------------------------------------------------------------------------------
/src/string/count-of.js:
--------------------------------------------------------------------------------
1 | import escapePattern from './escape-pattern';
2 |
3 | // the number of times substr appears within str
4 | export default function countOf(str, substr, caseSensitive) {
5 | const escapedStr = escapePattern(substr);
6 | const flags = (!caseSensitive) ? 'ig' : 'g';
7 | return str.match(new RegExp(escapedStr, flags)).length;
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/edit-distance.js:
--------------------------------------------------------------------------------
1 | // Levenshtein distance (editDistance) is a measure of the similarity between
2 | // two strings. The distance is the number of deletions, insertions, or
3 | // substitutions required to transform source into target.
4 | export default function editDistance(source = '', target = '') {
5 |
6 | if (source === target) {
7 | return 0;
8 | }
9 |
10 | if (!source.length) {
11 | return target.length;
12 | }
13 |
14 | if (!target.length) {
15 | return source.length;
16 | }
17 |
18 | const d = [];
19 | let i, j, cost;
20 |
21 | for (i = 0; i <= source.length; i++) {
22 | d[i] = [];
23 | }
24 | for (i = 0; i <= source.length; i++) {
25 | d[i][0] = i;
26 | }
27 | for (j = 0; j <= target.length; j++) {
28 | d[0][j] = j;
29 | }
30 |
31 | for (i = 1; i <= source.length; i++) {
32 |
33 | const si = source.charAt(i - 1);
34 | for (j = 1; j <= target.length; j++) {
35 |
36 | const tj = target.charAt(j - 1);
37 |
38 | if (si === tj) {
39 | cost = 0;
40 | } else {
41 | cost = 1;
42 | }
43 |
44 | d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
45 | }
46 | }
47 |
48 | return d[source.length][target.length];
49 | }
50 |
--------------------------------------------------------------------------------
/src/string/ends-with.js:
--------------------------------------------------------------------------------
1 | // whether str ends with substr
2 | export default function endsWith(str, substr) {
3 | return str.lastIndexOf(substr) === str.length - substr.length;
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/escape-html.js:
--------------------------------------------------------------------------------
1 | // export default function escapeHtml(str) {
2 | // const div = document.createElement('div');
3 | // div.appendChild(document.createTextNode(str));
4 | // return div.innerHTML;
5 | // }
6 |
7 | const entityMap = {
8 | '&': '&',
9 | '<': '<',
10 | '>': '>',
11 | '"': '"',
12 | '\'': ''',
13 | '/': '/',
14 | '`': '`',
15 | '=': '='
16 | };
17 |
18 | export default function escapeHtml(string) {
19 | return String(string)
20 | .replace(/[&<>"'`=\/]/g, function fromEntityMap(s) {
21 | return entityMap[s];
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/src/string/escape-pattern.js:
--------------------------------------------------------------------------------
1 | // regex escape pattern
2 | export default function escapePattern(pattern) {
3 | return pattern.replace(/(\]|\[|\{|\}|\(|\)|\*|\+|\?|\.|\\)/g, '\\$1');
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/has-text.js:
--------------------------------------------------------------------------------
1 | import removeExtraWhitespace from './remove-extra-whitespace';
2 |
3 | // whether str contains any text
4 | export default function hasText(str) {
5 | return !!removeExtraWhitespace(str).length;
6 | }
7 |
--------------------------------------------------------------------------------
/src/string/index.js:
--------------------------------------------------------------------------------
1 | import afterFirst from './after-first';
2 | import afterLast from './after-last';
3 | import beforeFirst from './before-first';
4 | import beforeLast from './before-last';
5 | import beginsWith from './begins-with';
6 | import between from './between';
7 | import block from './block';
8 | import capitalize from './capitalize';
9 | import countOf from './count-of';
10 | import editDistance from './edit-distance';
11 | import endsWith from './ends-with';
12 | import escapeHTML from './escape-html';
13 | import escapePattern from './escape-pattern';
14 | import hasText from './has-text';
15 | import isNumeric from './is-numeric';
16 | import padLeft from './pad-left';
17 | import padRight from './pad-right';
18 | import preventWidow from './prevent-widow';
19 | import properCase from './proper-case';
20 | import remove from './remove';
21 | import removeExtraWhitespace from './remove-extra-whitespace';
22 | import reverse from './reverse';
23 | import reverseWords from './reverse-words';
24 | import similarity from './similarity';
25 | import stripTags from './strip-tags';
26 | import swapCase from './swap-case';
27 | import timeCode from './time-code';
28 | import toNumber from './to-number';
29 | import truncate from './truncate';
30 | import wordCount from './word-count';
31 |
32 | export default {
33 | afterFirst,
34 | afterLast,
35 | beforeFirst,
36 | beforeLast,
37 | beginsWith,
38 | between,
39 | block,
40 | capitalize,
41 | countOf,
42 | editDistance,
43 | endsWith,
44 | escapeHTML,
45 | escapePattern,
46 | hasText,
47 | isNumeric,
48 | padLeft,
49 | padRight,
50 | preventWidow,
51 | properCase,
52 | remove,
53 | removeExtraWhitespace,
54 | reverse,
55 | reverseWords,
56 | similarity,
57 | stripTags,
58 | swapCase,
59 | timeCode,
60 | toNumber,
61 | truncate,
62 | wordCount
63 | };
64 |
--------------------------------------------------------------------------------
/src/string/is-numeric.js:
--------------------------------------------------------------------------------
1 | // whether str is numeric
2 | export default function isNumeric(str) {
3 | const regx = /^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/;
4 | return regx.test(str);
5 | }
6 |
--------------------------------------------------------------------------------
/src/string/pad-left.js:
--------------------------------------------------------------------------------
1 | // pad str with substr from the left
2 | export default function padLeft(str, substr, length) {
3 | str = String(str);
4 | while (str.length < length) {
5 | str = substr + str;
6 | }
7 | return str;
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/pad-right.js:
--------------------------------------------------------------------------------
1 | // pads str with substr from the right
2 | export default function padRight(str, substr, length) {
3 | str = String(str);
4 | while (str.length < length) {
5 | str += substr;
6 | }
7 | return str;
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/prevent-widow.js:
--------------------------------------------------------------------------------
1 | export default function preventWidow(str) {
2 | str = str.trim();
3 |
4 | const lastSpace = str.lastIndexOf(' ');
5 | if (lastSpace > 0) {
6 | return `${str.slice(0, lastSpace)} ${str.slice(lastSpace + 1)}`;
7 | }
8 |
9 | return str;
10 | }
11 |
--------------------------------------------------------------------------------
/src/string/proper-case.js:
--------------------------------------------------------------------------------
1 | import capitalize from './capitalize';
2 |
3 | // proper case str in sentence format
4 | export default function properCase(str) {
5 | const newStr = str.toLowerCase().replace(/\b([^.?;!]+)/, capitalize);
6 | return newStr.replace(/\b[i]\b/, 'I');
7 | }
8 |
--------------------------------------------------------------------------------
/src/string/remove-extra-whitespace.js:
--------------------------------------------------------------------------------
1 | // remove extra whitespace (extra spaces, tabs, line breaks, etc)
2 | export default function removeExtraWhitespace(str) {
3 | return str.trim().replace(/\s+/g, ' ');
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/remove.js:
--------------------------------------------------------------------------------
1 | import escapePattern from './escape-pattern';
2 |
3 | // remove all instances of substr in str
4 | export default function remove(str, substr, caseSensitive = false) {
5 | const escapedStr = escapePattern(substr);
6 | const flags = caseSensitive ? 'g' : 'ig';
7 | return str.replace(new RegExp(escapedStr, flags), '');
8 | }
9 |
--------------------------------------------------------------------------------
/src/string/reverse-words.js:
--------------------------------------------------------------------------------
1 | // reverse word order
2 | export default function reverseWords(str) {
3 | return str.split(' ').reverse().join(' ');
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/reverse.js:
--------------------------------------------------------------------------------
1 | // reverse character order
2 | export default function reverse(str) {
3 | return str.split('').reverse().join('');
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/similarity.js:
--------------------------------------------------------------------------------
1 | import editDistance from './edit-distance';
2 |
3 | // percentage of similiarity from 0 to 1
4 | export default function similarity(a, b) {
5 | const e = editDistance(a, b);
6 | const m = Math.max(a.length, b.length);
7 | if (m === 0) {
8 | return 1;
9 | }
10 | return (1 - e / m);
11 | }
12 |
--------------------------------------------------------------------------------
/src/string/strip-tags.js:
--------------------------------------------------------------------------------
1 | // remove all HTML tags from str
2 | export default function stripTags(str) {
3 | return str.replace(/<\/?[^>]+>/igm, '');
4 | }
5 |
--------------------------------------------------------------------------------
/src/string/swap-case.js:
--------------------------------------------------------------------------------
1 |
2 | // swaps the case of str
3 | export default function swapCase(str) {
4 | return str.replace(/(\w)/, function(newStr) {
5 | const lower = newStr.toLowerCase();
6 | const upper = newStr.toUpperCase();
7 | switch (newStr) {
8 | case lower:
9 | return upper;
10 | case upper:
11 | return lower;
12 | default:
13 | return newStr;
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/src/string/time-code.js:
--------------------------------------------------------------------------------
1 | // formats seconds into HH:MM:SS
2 | export default function timeCode(seconds, delim = ':') {
3 | const h = Math.floor(seconds / 3600);
4 | const m = Math.floor((seconds % 3600) / 60);
5 | const s = Math.floor((seconds % 3600) % 60);
6 | const hr = (h < 10 ? '0' + h : h) + delim;
7 | const mn = (m < 10 ? '0' + m : m) + delim;
8 | const sc = (s < 10 ? '0' + s : s);
9 | return hr + mn + sc;
10 | }
11 |
--------------------------------------------------------------------------------
/src/string/to-number.js:
--------------------------------------------------------------------------------
1 | export default function toNumber(str) {
2 | return Number(str.replace(/[^0-9.]/g, ''));
3 | }
4 |
--------------------------------------------------------------------------------
/src/string/truncate.js:
--------------------------------------------------------------------------------
1 | // truncate to length with suffix
2 | export default function truncate(str, len, suffix = '...') {
3 | len -= suffix.length;
4 | let trunc = str;
5 | if (trunc.length > len) {
6 | trunc = trunc.substr(0, len);
7 | const r = /[^\s]/;
8 | if (r.test(str.charAt(len))) {
9 | trunc = trunc.replace(/\w+$|\s+$/, '').trim();
10 | }
11 | trunc += suffix;
12 | }
13 | return trunc;
14 | }
15 |
--------------------------------------------------------------------------------
/src/string/word-count.js:
--------------------------------------------------------------------------------
1 | // the number of words in a string
2 | export default function wordCount(str) {
3 | return str.match(/\b\w+\b/g).length;
4 | }
5 |
--------------------------------------------------------------------------------
/src/track/event.js:
--------------------------------------------------------------------------------
1 | export default function event(category, action, label, value) {
2 | if (!window.ga) {
3 | return;
4 | }
5 | window.ga('send', 'event', category, action, label, value);
6 | }
7 |
--------------------------------------------------------------------------------
/src/track/index.js:
--------------------------------------------------------------------------------
1 | import event from './event';
2 | import pageview from './pageview';
3 | import load from './load';
4 |
5 | export default {
6 | event,
7 | pageview,
8 | load
9 | };
10 |
--------------------------------------------------------------------------------
/src/track/load.js:
--------------------------------------------------------------------------------
1 | export default function load(gaAccount) {
2 | console.log('Initialize Google Analytics with account Id:', gaAccount);
3 |
4 | /*eslint-disable*/
5 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
6 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
7 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
8 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
9 | /*eslint-enable*/
10 |
11 | window.ga('create', gaAccount, 'auto');
12 | window.ga('send', 'pageview');
13 | }
14 |
--------------------------------------------------------------------------------
/src/track/pageview.js:
--------------------------------------------------------------------------------
1 | export default function pageview(path) {
2 | if (!window.ga) {
3 | return;
4 | }
5 | window.ga('send', 'pageview', path);
6 | }
7 |
--------------------------------------------------------------------------------
/src/tween/index.js:
--------------------------------------------------------------------------------
1 | import {easeOutQuad} from '../ease/quad';
2 |
3 | export default class Tween {
4 | constructor(ob, props, duration, options) {
5 | this.ob = ob;
6 |
7 | if (props) {
8 | this.to(props, duration, options);
9 | }
10 | }
11 |
12 | to(props, duration, options = {}) {
13 | this.duration = duration;
14 | this.ease = options.ease || easeOutQuad;
15 | this.delay = options.delay || 0;
16 | this.onUpdate = options.onUpdate;
17 | this.onComplete = options.onComplete;
18 | this.time = 0;
19 | this.complete = false;
20 |
21 | this._props = Object.keys(props);
22 | this._beginVals = {};
23 | this._changeVals = {};
24 |
25 | for (let i = 0; i < this._props.length; i++) {
26 | const prop = this._props[i];
27 | this._beginVals[prop] = this.ob[prop];
28 | this._changeVals[prop] = props[prop] - this._beginVals[prop];
29 | }
30 | }
31 |
32 | update(dt) {
33 | if (this.time === this.duration) {
34 | return;
35 | }
36 |
37 | if (this.delay > 0) {
38 | this.delay -= dt;
39 | return;
40 | }
41 |
42 | this.time += dt;
43 |
44 | if (this.time > this.duration) {
45 | this.time = this.duration;
46 | }
47 |
48 | for (let i = 0; i < this._props.length; i++) {
49 | const prop = this._props[i];
50 | this.ob[prop] = this.ease(this.time, this._beginVals[prop], this._changeVals[prop], this.duration);
51 | }
52 |
53 | if (this.onUpdate) {
54 | this.onUpdate(this.ob);
55 | }
56 |
57 | if (this.time === this.duration) {
58 | this.complete = true;
59 |
60 | if (this.onComplete) {
61 | this.onComplete(this.ob);
62 | }
63 | }
64 | }
65 |
66 | reset() {
67 | this.time = 0;
68 | this.complete = false;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/visibility/api.js:
--------------------------------------------------------------------------------
1 | let hidden = null;
2 | let change = null;
3 |
4 | if (typeof document !== 'undefined') {
5 | if (typeof document.hidden !== 'undefined') {
6 | hidden = 'hidden';
7 | change = 'visibilitychange';
8 | } else if (typeof document.mozHidden !== 'undefined') {
9 | hidden = 'mozHidden';
10 | change = 'mozvisibilitychange';
11 | } else if (typeof document.msHidden !== 'undefined') {
12 | hidden = 'msHidden';
13 | change = 'msvisibilitychange';
14 | } else if (typeof document.webkitHidden !== 'undefined') {
15 | hidden = 'webkitHidden';
16 | change = 'webkitvisibilitychange';
17 | }
18 | }
19 |
20 | export default {
21 | hidden,
22 | change
23 | };
24 |
--------------------------------------------------------------------------------
/src/visibility/index.js:
--------------------------------------------------------------------------------
1 | import api from './api';
2 | import Emitter from '../events/emitter';
3 |
4 | const visibility = new Emitter();
5 |
6 | Object.defineProperties(visibility, {
7 | hidden: {
8 | get() {
9 | return document[api.hidden];
10 | }
11 | }
12 | });
13 |
14 | function onVisibilityChange() {
15 | if (document[api.hidden]) {
16 | visibility.emit('hidden');
17 | } else {
18 | visibility.emit('shown');
19 | }
20 | }
21 |
22 | if (api.change) {
23 | document.addEventListener(api.change, onVisibilityChange, false);
24 | }
25 |
26 | export default visibility;
27 |
--------------------------------------------------------------------------------
/test/array.spec.js:
--------------------------------------------------------------------------------
1 | import array from '../src/array';
2 |
3 | describe('array utils', () => {
4 |
5 | it('should clone array', () => {
6 | const arr = [1, 2, 3];
7 | const cloned = array.clone(arr);
8 | arr[0] = 9;
9 | expect(cloned).to.be.instanceof(Array);
10 | expect(cloned).to.eql([1, 2, 3]);
11 | });
12 |
13 | it('should find nearest value', () => {
14 | expect(array.nearest(2.2, [3, 2, 1, 0])).to.eql(2);
15 | expect(array.nearest(100, [300, 2000])).to.eql(300);
16 | expect(array.nearest(-10, [-9, 10])).to.eql(-9);
17 | });
18 |
19 | it('should move element', () => {
20 | expect(array.moveElement([1, 2, 3], 0, 1)).to.eql([2, 1, 3]);
21 | expect(array.moveElement([1, 2, 3], 2, 0)).to.eql([3, 1, 2]);
22 | });
23 |
24 | it('should return random element', () => {
25 | expect(array.randomChoice([3, 2, 1, 0])).to.be.a('number');
26 | });
27 |
28 | it('should return alpha ordered array', () => {
29 | expect(['thing3', 'thing2', 'thing1', 'thing0'].sort(array.sortAlpha))
30 | .to.eql(['thing0', 'thing1', 'thing2', 'thing3']);
31 | expect([{n: 'thing3'}, {n: 'thing2'}, {n: 'thing1'}].sort(array.sortAlpha('n')))
32 | .to.eql([{n: 'thing1'}, {n: 'thing2'}, {n: 'thing3'}]);
33 | });
34 |
35 | it('should return numbered ordered array', () => {
36 | expect(['Item 10', 'Item 2', 'Item 1'].sort(array.sortNumbered)).to.eql(['Item 1', 'Item 2', 'Item 10']);
37 | expect(['val=20', 'val=1', 'val=0.3'].sort(array.sortNumbered)).to.eql(['val=0.3', 'val=1', 'val=20']);
38 | expect(['val=-1', 'val=-3', 'val=-2'].sort(array.sortNumbered)).to.eql(['val=-3', 'val=-2', 'val=-1']);
39 | expect([{n: 'Item 2'}, {n: 'Item 3'}, {n: 'Item 1'}].sort(array.sortNumbered('n')))
40 | .to.eql([{n: 'Item 1'}, {n: 'Item 2'}, {n: 'Item 3'}]);
41 | });
42 |
43 | it('should return numeric ordered array', () => {
44 | expect([3, 2, 1, 0].sort(array.sortNumeric)).to.eql([0, 1, 2, 3]);
45 | expect([20, -1, 0.3].sort(array.sortNumeric)).to.eql([-1, 0.3, 20]);
46 | expect([{n: 2}, {n: 3}, {n: 1}].sort(array.sortNumeric('n')))
47 | .to.eql([{n: 1}, {n: 2}, {n: 3}]);
48 | });
49 |
50 | it('should return random sorted array', () => {
51 | expect([3, 2, 1, 0].sort(array.sortRandom)).to.be.instanceof(Array);
52 | expect([3, 2, 1, 0].sort(array.sortRandom)).to.have.property('length', 4);
53 | });
54 |
55 | it('should return unique array', () => {
56 | expect(array.unique([3, 2, 1, 0])).to.be.instanceof(Array);
57 | expect(array.unique([3, 2, 1, 0])).to.eql([3, 2, 1, 0]);
58 | expect(array.unique([3, 3, 1, 1])).to.eql([3, 1]);
59 | expect(array.unique([3, 3, 3, 3])).to.eql([3]);
60 | expect(array.unique([3, 3, 3, 2])).to.eql([3, 2]);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/test/bundle-nodejs.spec.js:
--------------------------------------------------------------------------------
1 | const usfl = require('../dist/usfl.js');
2 | const {expect} = require('chai');
3 |
4 | describe('node bundle', () => {
5 |
6 | it('should exist', () => {
7 | expect(usfl).to.exist;
8 | });
9 |
10 | it('should have array utils', () => {
11 | expect(usfl.array).to.be.an('object');
12 | });
13 |
14 | it('should have dom utils', () => {
15 | expect(usfl.dom).to.be.an('object');
16 | });
17 |
18 | it('should have ease util', () => {
19 | expect(usfl.ease).to.be.an('object');
20 | });
21 |
22 | it('should have events utils', () => {
23 | expect(usfl.events).to.be.an('object');
24 | });
25 |
26 | it('should have fullscreen util', () => {
27 | expect(usfl.fullscreen).to.be.an('object');
28 | });
29 |
30 | it('should have graphics utils', () => {
31 | expect(usfl.graphics).to.be.a('function');
32 | });
33 |
34 | it('should have gui util', () => {
35 | expect(usfl.gui).to.be.a('function');
36 | });
37 |
38 | it('should have http utils', () => {
39 | expect(usfl.http).to.be.an('object');
40 | });
41 |
42 | it('should have imput utils', () => {
43 | expect(usfl.input).to.be.an('object');
44 | });
45 |
46 | it('should have linkedList utils', () => {
47 | expect(usfl.linkedList).to.be.a('function');
48 | });
49 |
50 | it('should have loop utils', () => {
51 | expect(usfl.Loop).to.be.a('function');
52 | expect(usfl.loop).to.be.an('object');
53 | });
54 |
55 | it('should have math utils', () => {
56 | expect(usfl.math).to.be.an('object');
57 | });
58 |
59 | it('should have media utils', () => {
60 | expect(usfl.media).to.be.an('object');
61 | });
62 |
63 | it('should have object utils', () => {
64 | expect(usfl.object).to.be.an('object');
65 | });
66 |
67 | it('should have objectPool util', () => {
68 | expect(usfl.objectPool).to.be.a('function');
69 | });
70 |
71 | it('should have Particle util', () => {
72 | expect(usfl.Particle).to.be.a('function');
73 | });
74 |
75 | it('should have ParticleGroup util', () => {
76 | expect(usfl.ParticleGroup).to.be.a('function');
77 | });
78 |
79 | it('should have platform utils', () => {
80 | expect(usfl.platform).to.be.an('object');
81 | });
82 |
83 | it('should have popup util', () => {
84 | expect(usfl.popup).to.be.a('function');
85 | });
86 |
87 | it('should have QuadTree util', () => {
88 | expect(usfl.QuadTree).to.be.a('function');
89 | });
90 |
91 | it('should have share utils', () => {
92 | expect(usfl.share).to.be.an('object');
93 | });
94 |
95 | it('should have storage util', () => {
96 | expect(usfl.storage).to.be.an('object');
97 | });
98 |
99 | it('should have string utils', () => {
100 | expect(usfl.string).to.be.an('object');
101 | });
102 |
103 | it('should have tween util', () => {
104 | expect(usfl.Tween).to.be.a('function');
105 | });
106 |
107 | it('should have track util', () => {
108 | expect(usfl.track).to.be.an('object');
109 | });
110 |
111 | it('should have visibility util', () => {
112 | expect(usfl.visibility).to.be.an('object');
113 | });
114 |
115 | });
116 |
--------------------------------------------------------------------------------
/test/bundle.spec.js:
--------------------------------------------------------------------------------
1 | describe('bundle', () => {
2 |
3 | it('should exist', () => {
4 | expect(usfl).to.exist;
5 | });
6 |
7 | it('should have utils', () => {
8 | expect(usfl.array).to.be.an('object');
9 | expect(usfl.dom).to.be.an('object');
10 | expect(usfl.ease).to.be.an('object');
11 | expect(usfl.events).to.be.an('object');
12 | expect(usfl.fullscreen).to.be.an('object');
13 | expect(usfl.graphics).to.be.a('function');
14 | expect(usfl.gui).to.be.a('function');
15 | expect(usfl.http).to.be.an('object');
16 | expect(usfl.input).to.be.an('object');
17 | expect(usfl.linkedList).to.be.a('function');
18 | expect(usfl.Loop).to.be.a('function');
19 | expect(usfl.math).to.be.an('object');
20 | expect(usfl.media).to.be.an('object');
21 | expect(usfl.object).to.be.an('object');
22 | expect(usfl.objectPool).to.be.a('function');
23 | expect(usfl.Particle).to.be.a('function');
24 | expect(usfl.ParticleGroup).to.be.a('function');
25 | expect(usfl.platform).to.be.an('object');
26 | expect(usfl.popup).to.be.a('function');
27 | expect(usfl.QuadTree).to.be.a('function');
28 | expect(usfl.share).to.be.an('object');
29 | expect(usfl.storage).to.be.an('object');
30 | expect(usfl.string).to.be.an('object');
31 | expect(usfl.Tween).to.be.a('function');
32 | expect(usfl.track).to.be.an('object');
33 | expect(usfl.visibility).to.be.an('object');
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/test/dom.spec.js:
--------------------------------------------------------------------------------
1 | import dom from '../src/dom';
2 |
3 | describe('dom', () => {
4 |
5 | it('should block scrolling', () => {
6 | dom.blockScrolling(true);
7 | expect(document.body.style.overflow).to.eql('hidden');
8 | dom.blockScrolling(false);
9 | expect(document.body.style.overflow).to.eql('');
10 | });
11 |
12 | it('should have forceRedraw', () => {
13 | expect(dom.forceRedraw).to.be.a('function');
14 | });
15 |
16 | it('should get ScrollPercentage', () => {
17 | expect(dom.getScrollPercentage()).to.be.a('number');
18 | });
19 |
20 | it('should get PageHeight', () => {
21 | expect(dom.getPageHeight()).to.be.a('number');
22 | });
23 |
24 | it('should get ScrollRemaining', () => {
25 | expect(dom.getScrollRemaining()).to.be.a('number');
26 | });
27 |
28 | it('should get ScrollTop', () => {
29 | expect(dom.getScrollTop()).to.be.a('number');
30 | });
31 |
32 | it('should get SrcsetImage', () => {
33 | const srcsetA = `images/image_2048.jpg 2048w,
34 | images/image_640.jpg 640w,
35 | images/image_1536.jpg 1536w`;
36 | expect(dom.getSrcsetImage(srcsetA)).to.be.a('string');
37 | expect(dom.getSrcsetImage(srcsetA, 2048)).to.eql('images/image_2048.jpg');
38 | expect(dom.getSrcsetImage(srcsetA, 500)).to.eql('images/image_640.jpg');
39 | expect(dom.getSrcsetImage(srcsetA, 1280)).to.eql('images/image_1536.jpg');
40 | expect(dom.getSrcsetImage(srcsetA, 3000)).to.eql('images/image_2048.jpg');
41 | });
42 |
43 | it('should get isElementInViewport', () => {
44 | expect(dom.isElementInViewport(document.body)).to.be.a('boolean');
45 | });
46 |
47 | it('should get isPageEnd', () => {
48 | expect(dom.isPageEnd()).to.be.a('boolean');
49 | });
50 |
51 | it('should have resize', () => {
52 | expect(dom.resize).to.be.a('function');
53 | });
54 |
55 | it('should have scroll', () => {
56 | expect(dom.scroll).to.be.a('function');
57 | });
58 |
59 | it('should have transitionEnd', () => {
60 | expect(dom.transitionEnd).to.be.a('function');
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/test/ease.spec.js:
--------------------------------------------------------------------------------
1 | import ease from '../src/ease';
2 |
3 | describe('Ease', () => {
4 |
5 | const types = [
6 | 'Linear',
7 | 'Back',
8 | 'Bounce',
9 | 'Circular',
10 | 'Cubic',
11 | 'Elastic',
12 | 'Expo',
13 | 'Quad',
14 | 'Quart',
15 | 'Quint',
16 | 'Sine'
17 | ];
18 |
19 | it('should have ease type objects', () => {
20 | for (let i = 0; i < types.length; i++) {
21 | const easeType = ease[types[i].toLowerCase()];
22 | expect(easeType).to.be.an('object');
23 | expect(easeType.easeIn).to.be.a('function');
24 | expect(easeType.easeOut).to.be.a('function');
25 | expect(easeType.easeInOut).to.be.a('function');
26 | }
27 | });
28 |
29 | it('should have ease type functions', () => {
30 | expect(ease.easeLinear).to.be.a('function');
31 | for (let i = 1; i < types.length; i++) {
32 | expect(ease['easeIn' + types[i]]).to.be.a('function');
33 | expect(ease['easeOut' + types[i]]).to.be.a('function');
34 | expect(ease['easeInOut' + types[i]]).to.be.a('function');
35 | }
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/events.spec.js:
--------------------------------------------------------------------------------
1 | import events from '../src/events';
2 |
3 | describe('events', () => {
4 |
5 | it('should have debounce', () => {
6 | expect(events.debounce).to.be.a('function');
7 | });
8 |
9 | it('should have delegateEvents', () => {
10 | expect(events.delegateEvents).to.be.a('function');
11 | });
12 |
13 | it('should have emitter', () => {
14 | expect(events.emitter).to.be.a('function');
15 | });
16 |
17 | it('should have eventBus', () => {
18 | expect(events.eventBus).to.be.an('object');
19 | });
20 |
21 | it('should have heartbeat', () => {
22 | expect(events.heartbeat).to.be.a('function');
23 | expect(events.heartbeat().start).to.be.a('function');
24 | expect(events.heartbeat().stop).to.be.a('function');
25 | expect(events.heartbeat().update).to.be.a('function');
26 | });
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/test/fps.spec.js:
--------------------------------------------------------------------------------
1 | import fps from '../src/fps';
2 |
3 | describe('fps', () => {
4 |
5 | it('should have update fn', () => {
6 | expect(fps.update).to.be.a('function');
7 | expect(fps.auto).to.be.a('function');
8 | });
9 |
10 | });
11 |
--------------------------------------------------------------------------------
/test/fullscreen.spec.js:
--------------------------------------------------------------------------------
1 | import fullscreen from '../src/fullscreen';
2 |
3 | describe('fullscreen', function() {
4 |
5 | it('should return state booleans', function() {
6 | expect(fullscreen.isSupported).to.be.a('boolean');
7 | expect(fullscreen.isFullscreen).to.be.a('boolean');
8 | expect(fullscreen.enabled).to.be.a('boolean');
9 | });
10 |
11 | it('should have request and exit methods', function() {
12 | expect(fullscreen.request).to.be.a('function');
13 | expect(fullscreen.exit).to.be.a('function');
14 | expect(fullscreen.toggle).to.be.a('function');
15 | });
16 |
17 | it('should have emitter', function() {
18 | expect(fullscreen.on).to.be.a('function');
19 | expect(fullscreen.off).to.be.a('function');
20 | });
21 |
22 | });
23 |
--------------------------------------------------------------------------------
/test/graphics.spec.js:
--------------------------------------------------------------------------------
1 | import Graphics from '../src/graphics';
2 | import getImageDataURL from '../src/graphics/get-image-data-url';
3 | import SpritesheetPlayer from '../src/graphics/spritesheet-player';
4 |
5 | describe('graphics', () => {
6 | const gfx = new Graphics();
7 |
8 | it('should construct successfully', () => {
9 | expect(gfx).to.exist;
10 | expect(gfx.size).to.be.a('function');
11 | expect(gfx.size(100, 100)).to.eql(gfx);
12 | });
13 |
14 | it('should have context', () => {
15 | expect(gfx.context).to.exist;
16 | });
17 |
18 | const image = new Image();
19 | image.crossOrigin = 'anonymous';
20 |
21 | beforeEach((done) => {
22 | if (!image.src) {
23 | image.addEventListener('load', () => done());
24 | image.src = 'http://i.imgur.com/pIUsuyE.jpg';
25 | } else {
26 | done();
27 | }
28 | });
29 |
30 | it('should get image data', () => {
31 | const dataURL = getImageDataURL(image);
32 | expect(dataURL.indexOf('data:image/')).to.eql(0);
33 | });
34 |
35 | // context: ctx,
36 | // size,
37 | // clear,
38 | // background,
39 | // fill,
40 | // stroke,
41 | // strokeWeight,
42 | // move,
43 | // line,
44 | // rect,
45 | // circle,
46 | // triangle,
47 | // triangleABC,
48 | // image,
49 | // cross,
50 | // text,
51 | // setFont,
52 | // setFontSize,
53 | // openImage,
54 | // downloadImage,
55 | // getImageData,
56 | // getPixel,
57 | // setPixel,
58 | // eachPixel
59 |
60 | });
61 |
62 | describe('spritesheet player', () => {
63 | it('should construct', () => {
64 | const p = new SpritesheetPlayer();
65 | expect(p).to.exist;
66 | expect(p.update).to.be.a('function');
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/test/http.spec.js:
--------------------------------------------------------------------------------
1 | import http from '../src/http';
2 |
3 | describe('http', () => {
4 |
5 | it('should get location', () => {
6 | expect(http.getLocation('http://www.example.com/path').hostname).to.eql('www.example.com');
7 | expect(http.getLocation('http://www.example.com/path').pathname).to.eql('/path');
8 | expect(http.getLocation('http://www.example.com/path').protocol).to.eql('http:');
9 | });
10 |
11 | it('should have jsonp', () => {
12 | expect(http.jsonp).to.be.a('function');
13 | });
14 |
15 | it('should get url params', () => {
16 | expect(http.urlParams('foo=bar&hello=world')).to.eql({
17 | foo: 'bar',
18 | hello: 'world'
19 | });
20 | });
21 |
22 | });
23 |
24 | describe('http.xhr', function() {
25 | this.timeout(5000);
26 |
27 | let res = null;
28 |
29 | it('should have xhr', () => {
30 | expect(http.xhr).to.be.a('function');
31 | });
32 |
33 | beforeEach((done) => {
34 | http.xhr('https://ianmcgregor.co/prototypes/test/test.json')
35 | .then((response) => (res = response))
36 | .then(() => done())
37 | .catch((err) => console.log('err', err));
38 | });
39 |
40 | it('should have json response', () => {
41 | expect(res).to.be.an('object');
42 | expect(res.name).to.eql('test');
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/test/input.spec.js:
--------------------------------------------------------------------------------
1 | import input from '../src/input';
2 |
3 | describe('input', () => {
4 |
5 | describe('clickOutside', () => {
6 | expect(input.clickOutside).to.be.a('function');
7 | expect(input.clickOutside().destroy).to.be.a('function');
8 | });
9 |
10 | describe('key', () => {
11 |
12 | it('should have keyboard constants', () => {
13 | expect(input.keyboard.A).to.eql(65);
14 | expect(input.keyboard.UP).to.eql(38);
15 | });
16 |
17 | it('should have key input', () => {
18 | expect(input.keyInput).to.be.a('function');
19 | expect(input.keyInput().enable).to.be.a('function');
20 | expect(input.keyInput().isDown).to.be.a('function');
21 | expect(input.keyInput().isDown(input.keyboard.UP)).to.be.a('boolean');
22 | });
23 |
24 | });
25 |
26 | describe('microphone', () => {
27 | expect(input.microphone).to.be.a('function');
28 | expect(input.microphone().isSupported()).to.be.a('boolean');
29 | });
30 |
31 | describe('mouseLeftWindow', () => {
32 | expect(input.mouseLeftWindow).to.be.a('function');
33 | expect(input.mouseLeftWindow(() => {}).destroy).to.be.a('function');
34 | });
35 |
36 | describe('mouseWheel', () => {
37 | expect(input.mouseWheel).to.be.a('function');
38 | });
39 |
40 | describe('pointerCoords', () => {
41 | expect(input.pointerCoords).to.be.a('function');
42 | expect(input.pointerCoords().x).to.be.a('number');
43 | expect(input.pointerCoords().y).to.be.a('number');
44 | expect(input.pointerCoords().percentX).to.be.a('number');
45 | expect(input.pointerCoords().percentY).to.be.a('number');
46 | });
47 |
48 | describe('touch input', () => {
49 | const touchInput = input.touchInput();
50 |
51 | it('should construct successfully', () => {
52 | expect(touchInput).to.be.an('object');
53 | });
54 |
55 | it('should have api', () => {
56 | expect(touchInput.listen).to.be.a('function');
57 | expect(touchInput.isDown).to.be.a('function');
58 | expect(touchInput.getTouch).to.be.a('function');
59 | expect(touchInput.destroy).to.be.a('function');
60 | });
61 |
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/test/linked-list.spec.js:
--------------------------------------------------------------------------------
1 | import linkedList from '../src/linked-list';
2 |
3 | describe('linked list', () => {
4 | const list = linkedList();
5 | const items = [];
6 |
7 | for (let i = 0; i < 10; i++) {
8 | items.push(list.add({
9 | 'index': i,
10 | 'next': null,
11 | 'prev': null
12 | }));
13 | }
14 |
15 | it('should have 10 items', () => {
16 | expect(list.getCount()).to.eql(10);
17 | });
18 |
19 | it('should have first', () => {
20 | expect(list.first).to.exist;
21 | expect(list.getFirst()).to.exist;
22 | });
23 |
24 | it('should have last', () => {
25 | expect(list.last).to.exist;
26 | expect(list.getLast()).to.exist;
27 | });
28 |
29 | it('should be able to iterate', () => {
30 | let item = list.getFirst();
31 | while (item.next) {
32 | expect(item).to.exist;
33 | expect(item).to.have.property('index');
34 | item = item.next;
35 | }
36 | expect(item).to.eql(list.getLast());
37 | });
38 |
39 | it('should be able to remove item', () => {
40 | list.remove(list.getFirst());
41 | expect(list.getCount()).to.eql(9);
42 | expect(list.getFirst()).to.exist;
43 |
44 | list.remove(list.getLast());
45 | expect(list.getCount()).to.eql(8);
46 | expect(list.getLast()).to.exist;
47 | });
48 |
49 | it('should be able to insert', () => {
50 | const item = {
51 | 'index': 100,
52 | 'next': null,
53 | 'prev': null
54 | };
55 | list.insertBefore(item, list.getFirst());
56 |
57 | expect(list.getCount()).to.eql(9);
58 | expect(item).to.eql(list.getFirst());
59 | });
60 |
61 | it('should be have getters', () => {
62 | expect(list.length).to.eql(9);
63 | expect(list.length).to.eql(list.getCount());
64 | expect(list.first).to.exist;
65 | expect(list.first).to.eql(list.getFirst());
66 | expect(list.last).to.exist;
67 | expect(list.last).to.eql(list.getLast());
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/test/loop.spec.js:
--------------------------------------------------------------------------------
1 | import {Loop} from '../src/loop';
2 |
3 | describe('Loop', () => {
4 | const loop = new Loop();
5 | let updated = false;
6 | let delta = 0;
7 | let elapsed = 0;
8 | loop.add((deltaTime, elapsedTime) => {
9 | updated = true;
10 | delta = deltaTime;
11 | elapsed = elapsedTime;
12 | });
13 |
14 | beforeEach((done) => {
15 | loop.start();
16 | done();
17 | });
18 |
19 | it('should construct', () => {
20 | expect(loop).to.exist;
21 | });
22 |
23 | it('should start', () => {
24 | loop.start();
25 | expect(loop.running).to.be.true;
26 | });
27 |
28 | it('should stop', () => {
29 | loop.stop();
30 | expect(loop.running).to.be.false;
31 | });
32 |
33 | it('should update', () => {
34 | expect(updated).to.be.true;
35 | });
36 |
37 | it('should have dt', () => {
38 | expect(delta).to.be.a('number');
39 | expect(elapsed).to.be.a('number');
40 | });
41 |
42 | });
43 |
--------------------------------------------------------------------------------
/test/media.spec.js:
--------------------------------------------------------------------------------
1 | import media from '../src/media';
2 | import mp4 from '../src/platform/mp4';
3 |
4 | describe('media', () => {
5 |
6 | describe('cuepointsReader', () => {
7 | let name = '';
8 | const reader = media.cuepointsReader();
9 | reader.onCuepoint((item) => (name = item.name));
10 | reader.add(2, 'foo', {name: 'bar'});
11 | reader.add(3, 'bar', {name: 'foo'});
12 |
13 | beforeEach((done) => {
14 | reader.update(2);
15 | done();
16 | });
17 |
18 | expect(media.cuepointsReader).to.be.a('function');
19 |
20 | it('should have dispatched foo', () => {
21 | expect(name).to.eql('foo');
22 | });
23 | });
24 |
25 | describe('video', () => {
26 | it('should have iOSPlayVideoInline', () => {
27 | expect(media.iOSPlayVideoInline).to.be.a('function');
28 | });
29 |
30 | it('should have videoPlayer', () => {
31 | expect(media.videoPlayer).to.be.a('function');
32 | });
33 |
34 | it('should have vimeo', () => {
35 | expect(media.vimeo).to.be.a('function');
36 | });
37 |
38 | it('should have youtube', () => {
39 | expect(media.youtube).to.be.a('function');
40 | });
41 |
42 | it('should have youtubeBasic', () => {
43 | expect(media.youtubeBasic).to.be.a('function');
44 | });
45 | });
46 |
47 | describe('video player', function() {
48 | this.timeout(20000); // extend timeout for this test
49 |
50 | const videoPlayer = media.videoPlayer(),
51 | ext = mp4 ? 'mp4' : 'webm',
52 | file = `https://ianmcgregor.co/prototypes/video/counter.${ext}`;
53 |
54 | let ready = false;
55 |
56 | beforeEach((done) => {
57 |
58 | videoPlayer.on('ready', function() {
59 | ready = true;
60 | done();
61 | }).on('error', function(err) {
62 | console.error(err);
63 | ready = true;
64 | done();
65 | });
66 | videoPlayer.load(file);
67 | });
68 |
69 | it('should be ready', function() {
70 | expect(ready).to.be.true;
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/test/object-pool.spec.js:
--------------------------------------------------------------------------------
1 | import objectPool from '../src/object-pool';
2 |
3 | describe('object pool', function() {
4 | let newlyCreated = 0;
5 |
6 | function TestOb() {
7 | newlyCreated++;
8 | const id = Math.random();
9 | return {
10 | getId: function() {
11 | return id;
12 | }
13 | };
14 | }
15 |
16 | it('should pass', function() {
17 | const pool = objectPool(TestOb);
18 | let instance = pool.get();
19 |
20 | expect(instance).to.exist;
21 | expect(instance.getId()).to.be.a('number');
22 | expect(pool.getPool().length).to.eql(0);
23 |
24 | pool.dispose(instance);
25 |
26 | expect(pool.getPool().length).to.eql(1);
27 |
28 | instance = pool.get();
29 |
30 | expect(instance.getId()).to.be.a('number');
31 | expect(pool.getPool().length).to.eql(0);
32 | expect(newlyCreated).to.eql(1);
33 |
34 | pool.fill(10);
35 | expect(pool.getPool().length).to.eql(10);
36 | expect(newlyCreated).to.eql(11);
37 |
38 | for (let i = 0; i < 5; i++) {
39 | instance = pool.get();
40 | expect(instance.getId()).to.be.a('number');
41 | }
42 | expect(pool.getPool().length).to.eql(5);
43 | expect(newlyCreated).to.eql(11);
44 |
45 | expect(pool.getNumCreated()).to.eql(newlyCreated);
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/object.spec.js:
--------------------------------------------------------------------------------
1 | import object from '../src/object';
2 |
3 | describe('object utils', () => {
4 |
5 | it('should clone object', () => {
6 | const obj = {a: 1, b: 2, c: 3};
7 | const cloned = object.clone(obj);
8 | obj.a = 9;
9 | expect(cloned).to.be.an('object');
10 | expect(cloned).to.eql({a: 1, b: 2, c: 3});
11 | expect(obj).to.eql({a: 9, b: 2, c: 3});
12 | });
13 |
14 | it('should filter object', () => {
15 | expect(object.filter({a: 1, b: 2, c: 3}, (key, value) => value > 1)).to.eql({b: 2, c: 3});
16 | expect(object.filter({a: 1, b: 2, c: 3}, key => key === 'a')).to.eql({a: 1});
17 | });
18 |
19 | it('should map object', () => {
20 | expect(object.map({a: 1, b: 2, c: 3}, (key, value) => value * 2)).to.eql({a: 2, b: 4, c: 6});
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/particle.spec.js:
--------------------------------------------------------------------------------
1 | import Particle from '../src/particle';
2 | import ParticleGroup from '../src/particle/particle-group';
3 |
4 | describe('Particle', () => {
5 |
6 | it('should construct Particle instance', () => {
7 | expect(new Particle()).to.exist;
8 | });
9 | });
10 |
11 | describe('ParticleGroup', () => {
12 |
13 | it('should construct ParticleGroup instance', () => {
14 | expect(new ParticleGroup()).to.exist;
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/test/platform.spec.js:
--------------------------------------------------------------------------------
1 | import platform from '../src/platform';
2 | import androidNative from '../src/platform/android-native';
3 | import ieVersion from '../src/platform/ie-version';
4 | import iosVersion from '../src/platform/ios-version';
5 | import androidVersion from '../src/platform/android-version';
6 |
7 | describe('platform', () => {
8 |
9 | it('should get device', () => {
10 | expect(platform.mobile).to.be.false;
11 | expect(platform.ipad).to.be.false;
12 | expect(platform.iphone).to.be.false;
13 | expect(platform.desktop).to.be.true;
14 | });
15 |
16 | it('should get os', () => {
17 | expect(platform.ios).to.be.false;
18 | expect(platform.mac).to.be.a('boolean');
19 | expect(platform.android).to.be.false;
20 | expect(platform.windows).to.be.false;
21 | });
22 |
23 | it('should get browser', () => {
24 | expect(androidNative('Mozilla/5.0 (Linux; U; Android 2.3.3; de-ch; HTC Desire Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1')).to.be.true;
25 | expect(androidNative('Mozilla/5.0 (Linux; U; Android 4.2; en-us; Nexus 10 Build/JVP15I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30')).to.be.true;
26 | expect(ieVersion()).to.eql(0);
27 | expect(platform.ie).to.be.false;
28 | });
29 |
30 | it('should get language', () => {
31 | expect(platform.language).to.be.a('string');
32 | });
33 |
34 | it('should get screen props', () => {
35 | expect(platform.screen.width).to.eql(window.screen.width);
36 | expect(platform.screen.height).to.eql(window.screen.height);
37 | expect(platform.screen.dpr).to.be.a('number');
38 | });
39 |
40 | it('should get ie version', () => {
41 | expect(ieVersion('Mozilla/5.0 (Linux; Android 4.1; Galaxy Nexus Build/JRN84D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19')).to.eql(0);
42 | expect(ieVersion('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; MS-RTC LM 8; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET CLR 3.0.30729)')).to.eql(8);
43 | expect(ieVersion('Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)')).to.eql(9);
44 | expect(ieVersion('Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)')).to.eql(10);
45 | expect(ieVersion('Mozilla/5.0 (IE 11.0; Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko')).to.eql(11);
46 | });
47 |
48 | it('should get ios version', () => {
49 | expect(iosVersion('Mozilla/5.0 (Linux; Android 4.1; Galaxy Nexus Build/JRN84D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19')).to.eql(0);
50 | expect(iosVersion('Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53')).to.eql(7);
51 | expect(iosVersion('Mozilla/5.0 (iPad; CPU OS 9_0 like Mac OS X) AppleWebKit/601.1.17 (KHTML, like Gecko) Version/8.0 Mobile/13A175 Safari/600.1.4')).to.eql(9);
52 | expect(iosVersion('Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13C75 Safari/601.1')).to.eql(9.2);
53 | });
54 |
55 | it('should get android version', () => {
56 | expect(androidVersion('Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53')).to.eql(0);
57 | expect(androidVersion('Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30')).to.eql(4);
58 | expect(androidVersion('Mozilla/5.0 (Linux; U; Android 2.3.3; de-ch; HTC Desire Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1')).to.eql(2.3);
59 | expect(androidVersion('Mozilla/5.0 (Linux; U; Android 4.2; en-us; Nexus 10 Build/JVP15I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30')).to.eql(4.2);
60 | expect(androidVersion('Mozilla/5.0 (Linux; Android 4.1; Galaxy Nexus Build/JRN84D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19')).to.eql(4.1);
61 | });
62 |
63 | });
64 |
--------------------------------------------------------------------------------
/test/popup.spec.js:
--------------------------------------------------------------------------------
1 | import popup from '../src/popup';
2 |
3 | describe('popup', () => {
4 |
5 | it('should pass', () => {
6 | expect(popup).to.be.a('function');
7 | });
8 |
9 | });
10 |
--------------------------------------------------------------------------------
/test/quad-tree.spec.js:
--------------------------------------------------------------------------------
1 | import QuadTree from '../src/quad-tree';
2 |
3 | describe('QuadTree', () => {
4 |
5 | it('should construct', () => {
6 | expect(new QuadTree({x: 0, y: 0, width: 100, height: 100})).to.exist;
7 | });
8 |
9 | });
10 |
--------------------------------------------------------------------------------
/test/share.spec.js:
--------------------------------------------------------------------------------
1 | import share from '../src/share';
2 |
3 | describe('share', () => {
4 |
5 | it('should have share functions', () => {
6 | expect(share.email).to.be.a('function');
7 | expect(share.facebook).to.be.a('function');
8 | expect(share.googleplus).to.be.a('function');
9 | expect(share.linkedin).to.be.a('function');
10 | expect(share.pinterest).to.be.a('function');
11 | expect(share.reddit).to.be.a('function');
12 | expect(share.renren).to.be.a('function');
13 | expect(share.sms).to.be.a('function');
14 | expect(share.twitter).to.be.a('function');
15 | expect(share.vkontakte).to.be.a('function');
16 | expect(share.weibo).to.be.a('function');
17 | expect(share.whatsapp).to.be.a('function');
18 | });
19 |
20 | });
21 |
--------------------------------------------------------------------------------
/test/storage.spec.js:
--------------------------------------------------------------------------------
1 | import storage from '../src/storage';
2 |
3 | describe('storage utils', () => {
4 |
5 | const key = 'testData',
6 | testData = {
7 | id: 'foo',
8 | name: 'bar',
9 | x: 0
10 | };
11 |
12 | it('should store object and return true', () => {
13 | const saved = storage.saveJSON(key, testData);
14 | expect(saved).to.be.true;
15 | });
16 |
17 | it('should retrieve stored object', () => {
18 | const loaded = storage.loadJSON(key);
19 | expect(loaded).to.exist;
20 | expect(loaded.id).to.eql('foo');
21 | expect(loaded.name).to.eql('bar');
22 | expect(loaded.x).to.eql(0);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/string.spec.js:
--------------------------------------------------------------------------------
1 | import string from '../src/string';
2 |
3 | describe('string utils', function() {
4 |
5 | const str = 'Hello World';
6 |
7 | it('should query', () => {
8 | expect(string.countOf(str, 'l')).to.eql(3);
9 | expect(string.endsWith(str, 'ld')).to.be.true;
10 | expect(string.hasText(str)).to.be.true;
11 | expect(string.isNumeric(str)).to.be.false;
12 | expect(string.isNumeric('68769123214')).to.be.true;
13 | expect(string.wordCount(str)).to.eql(2);
14 | expect(string.similarity(str, str)).to.eql(1);
15 | });
16 |
17 | it('should find editDistance', () => {
18 | expect(string.editDistance(str, str)).to.eql(0);
19 | expect(string.editDistance(str, str + 'a')).to.eql(1);
20 | });
21 |
22 | it('should find substr', () => {
23 | expect(string.afterFirst(str, 'l')).to.eql('lo World');
24 | expect(string.afterLast(str, 'l')).to.eql('d');
25 | expect(string.beginsWith(str, 'H')).to.be.true;
26 | expect(string.beforeFirst(str, 'l')).to.eql('He');
27 | expect(string.beforeLast(str, 'l')).to.eql('Hello Wor');
28 | expect(string.between(str, 'H', 'W')).to.eql('ello ');
29 | });
30 |
31 | it('should format', () => {
32 | expect(string.padLeft(str, '_', 12)).to.eql('_Hello World');
33 | expect(string.padRight(str, '_', 12)).to.eql('Hello World_');
34 | expect(string.removeExtraWhitespace('Hello World')).to.eql('Hello World');
35 | expect(string.remove(str, 'll')).to.eql('Heo World');
36 | // TODO: this sometime acts unexpectedly with shorter strings
37 | expect(string.truncate(str, 10)).to.eql('Hello...');
38 | //expect(string.truncate(str, 4)).to.eql('Hello...');
39 | expect(string.capitalize(str.toLowerCase())).to.eql('Hello world');
40 | expect(string.properCase(str.toLowerCase())).to.eql('Hello World');
41 | expect(string.reverse(str)).to.eql('dlroW olleH');
42 | expect(string.reverseWords(str)).to.eql('World Hello');
43 | expect(string.stripTags('
' + str + '
')).to.eql('Hello World'); 44 | expect(string.swapCase(str)).to.eql('hello World'); 45 | // expect(string.block(str)).to.eql('Hello World'); 46 | expect(string.timeCode(217.8)).to.eql('00:03:37'); 47 | }); 48 | 49 | it('should escape', () => { 50 | expect(string.escapePattern(str + '.')).to.eql('Hello World\\.'); 51 | expect(string.escapeHTML('')) 52 | .to.eql('<script>alert("lol")</script>'); 53 | }); 54 | 55 | it('should prevent widow', () => { 56 | expect(string.preventWidow('Hello world')).to.eql('Hello world'); 57 | expect(string.preventWidow(' Hello world ')).to.eql('Hello world'); 58 | expect(string.preventWidow('Morbi in sem quis dui placerat ornare.')) 59 | .to.eql('Morbi in sem quis dui placerat ornare.'); 60 | }); 61 | 62 | it('should convert to number', () => { 63 | expect(string.toNumber('thing_01')).to.eql(1); 64 | expect(string.toNumber('123%')).to.eql(123); 65 | expect(string.toNumber('0.10')).to.eql(0.1); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/track.spec.js: -------------------------------------------------------------------------------- 1 | import track from '../src/track'; 2 | 3 | describe('track', () => { 4 | 5 | it('should have event function', () => { 6 | expect(track.event).to.be.a('function'); 7 | }); 8 | 9 | it('should have pageview function', () => { 10 | expect(track.pageview).to.be.a('function'); 11 | }); 12 | 13 | it('should have load function', () => { 14 | expect(track.load).to.be.a('function'); 15 | expect(track.load.length).to.eql(1); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /test/tween.spec.js: -------------------------------------------------------------------------------- 1 | import Tween from '../src/tween'; 2 | 3 | describe('Tween', () => { 4 | const ob = {x: 0}; 5 | const tween = new Tween(ob, {x: 10}, 1); 6 | 7 | it('should construct', () => { 8 | expect(tween).to.exist; 9 | }); 10 | 11 | it('should change prop', () => { 12 | tween.update(0.5); 13 | tween.update(0.5); 14 | expect(ob.x).to.eql(10); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /test/visibility.spec.js: -------------------------------------------------------------------------------- 1 | import api from '../src/visibility/api'; 2 | import visibility from '../src/visibility'; 3 | 4 | describe('visibility', () => { 5 | 6 | it('should get visibility api from browser', () => { 7 | expect(api.hidden).to.be.a('string'); 8 | expect(api.change).to.be.a('string'); 9 | }); 10 | 11 | it('should have hidden property', function() { 12 | expect(visibility.hidden).to.be.a('boolean'); 13 | }); 14 | 15 | it('should be emitter', function() { 16 | expect(visibility.on).to.be.a('function'); 17 | expect(visibility.off).to.be.a('function'); 18 | }); 19 | 20 | }); 21 | --------------------------------------------------------------------------------