├── .eslintrc ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── dist ├── siriwave.esm.js ├── siriwave.esm.min.js ├── siriwave.umd.js ├── siriwave.umd.min.js └── types │ ├── classic-curve.d.ts │ ├── index.d.ts │ └── ios9-curve.d.ts ├── etc ├── classic.gif ├── dat.gui.js ├── gcx │ ├── default.gcx │ └── ios9.gcx └── ios9.gif ├── index.html ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── classic-curve.ts ├── index.ts └── ios9-curve.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | "prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 6 | "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.], 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["prettier"], 13 | "env": { 14 | "browser": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 18 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm install 20 | - run: npm run build 21 | - run: npm publish 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 24 | 25 | publish-gpr: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: 18 32 | registry-url: https://npm.pkg.github.com/ 33 | - run: npm install 34 | - run: npm run build 35 | - run: npm publish 36 | env: 37 | NODE_AUTH_TOKEN: ${{secrets.GH_PKG_TOKEN}} 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn-error.log -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": false, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Flavio Maria De Stefano 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 | # SiriWave 2 | 3 | The "Apple Siri" wave replicated in pure Javascript using the Canvas API. To learn more about the project, [read the blog post here](https://dev.to/kopiro/how-i-built-the-siriwavejs-library-a-look-at-the-math-and-the-code-l0o), [check the demo](http://kopiro.github.io/siriwave) or [codepen](https://codepen.io/kopiro/pen/oNYepEb). 4 | 5 | [![npm version](https://badge.fury.io/js/siriwave.svg)](https://badge.fury.io/js/siriwave) 6 | 7 | ### iOS (classic) style 8 | 9 | The classic, pre-iOS9 style. 10 | 11 | 12 | 13 | ### iOS9 style 14 | 15 | The new fluorescent wave introduced in iOS9. 16 | 17 | 18 | 19 | ### iOS13 style 20 | 21 | _work in progress_ 22 | 23 | The wave reinvented as a bubble. 24 | 25 | ## Usage 26 | 27 | ### Browser (via CDN) usage 28 | 29 | Import the UMD package via the unpkg CDN and it's ready to use. 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ### ES module 36 | 37 | Install it through `npm install siriwave` or `npm add siriwave` first: 38 | 39 | ```js 40 | import SiriWave from "siriwave"; 41 | ``` 42 | 43 | ## Initialize 44 | 45 | Create a div container and instantiate a new SiriWave object: 46 | 47 | ```html 48 |
49 | 56 | ``` 57 | 58 | ## Constructor options 59 | 60 | | Key | Type | Description | Default | Required | 61 | | -------------------------- | ------------------------ | ---------------------------------------------------------------------- | ---------- | -------- | 62 | | `container` | DOMElement | The DOM container where the DOM canvas element will be added. | null | yes | 63 | | `style` | "ios", "ios9" | The style of the wave. | "ios" | no | 64 | | `ratio` | Number | Ratio of the display to use. Calculated by default. | calculated | no | 65 | | `speed` | Number | The speed of the animation. | 0.2 | no | 66 | | `amplitude` | Number | The amplitude of the complete wave-form. | 1 | no | 67 | | `frequency` | Number | The frequency of the complete wave-form. Only available in style "ios" | 6 | no | 68 | | `color` | String | Color of the wave. Only available in style "ios" | "#fff" | no | 69 | | `cover` | Bool | The `canvas` covers the entire width or height of the container | false | no | 70 | | `autostart` | Bool | Decide wether start the animation on boot. | false | no | 71 | | `pixelDepth` | Number | Number of step (in pixels) used when drawed on canvas. | 0.02 | no | 72 | | `lerpSpeed` | Number | Lerp speed to interpolate properties. | 0.01 | no | 73 | | `curveDefinition` | ICurveDefinition[] | Override definition of the curves, check above for more details. | null | no | 74 | | `ranges` | IiOS9Ranges | Override the default random ranges of the curves. | null | no | 75 | | `globalCompositeOperation` | GlobalCompositeOperation | globalCompositeOperation of the canvas, controls wave overlap design. | "lighter" | no | 76 | 77 | ### Ranges 78 | 79 | Each wave chooses a random parameter for each of these ranges that factors into their creation. You can override these ranges by passing a `ranges` object to the constructor. 80 | 81 | Here is the type of the ranges object: 82 | 83 | ```ts 84 | export type IiOS9Ranges = { 85 | noOfCurves?: [number, number]; 86 | amplitude?: [number, number]; 87 | offset?: [number, number]; 88 | width?: [number, number]; 89 | speed?: [number, number]; 90 | despawnTimeout?: [number, number]; 91 | }; 92 | ``` 93 | 94 | ## API 95 | 96 | #### `new SiriWave` 97 | 98 | #### `curveDefinition` 99 | 100 | By passing this argument, you're overriding the default curve definition resulting in a completely different style. 101 | 102 | The default definition for the `ios` classic style is: 103 | 104 | ```js 105 | [ 106 | { attenuation: -2, lineWidth: 1, opacity: 0.1 }, 107 | { attenuation: -6, lineWidth: 1, opacity: 0.2 }, 108 | { attenuation: 4, lineWidth: 1, opacity: 0.4 }, 109 | { attenuation: 2, lineWidth: 1, opacity: 0.6 }, 110 | { attenuation: 1, lineWidth: 1.5, opacity: 1 }, 111 | ]; 112 | ``` 113 | 114 | and it results in 5 different sin-waves with different parameters and amplitude. 115 | 116 | You can set 4 attributes for each curve: 117 | 118 | - `attenuation`: the power factor determining the attenuation 119 | - `lineWidth`: the line width 120 | - `opacity`: the opacity 121 | - `color`: the color, default to `SiriWave.color`; optional 122 | 123 | The `ios9` style definition is instead: 124 | 125 | ```js 126 | [ 127 | { color: "255,255,255", supportLine: true }, 128 | { color: "15, 82, 169" }, // blue 129 | { color: "173, 57, 76" }, // red 130 | { color: "48, 220, 155" }, // green 131 | ]; 132 | ``` 133 | 134 | and it results in 3 different colored waves + 1 support wave that needs to be there. 135 | 136 | Here you set: 137 | 138 | - `supportLine`: only one of these curves must have this to `true`, it will be used to draw the support line 139 | - `color`: the color of the wave 140 | 141 | #### `start()` 142 | 143 | Start the animation 144 | 145 | #### `stop()` 146 | 147 | Stop the animation. 148 | 149 | #### `setSpeed(newValue)` 150 | 151 | Set the new value of speed (animated) 152 | 153 | #### `setAmplitude(value)` 154 | 155 | Set the new value of amplitude (animated) 156 | 157 | #### `dispose()` 158 | 159 | Stop the animation and destroy the canvas, by removing it from the DOM. 160 | Subsequent `start()` calls on this SiriWave instance will fail with an exception. 161 | 162 | ## Grapher plots 163 | 164 | - [GCX default](etc/gcx/default.gcx) 165 | - [GCX iOS 9](etc/gcx/ios9.gcx) 166 | 167 | ## Build and development 168 | 169 | If you wanna make some modifications in your local environment, use: 170 | 171 | ```sh 172 | npm dev 173 | ``` 174 | 175 | this will create a watchable build with RollupJS and automatically create a server to see your changes in the browser. 176 | 177 | To finally build all targets: 178 | 179 | ```sh 180 | npm build 181 | ``` 182 | 183 | ## QA 184 | 185 | #### How do I integrate this library with a microphone user input? 186 | 187 | You can find an excellent demo [here](https://jsitor.com/PPQtOp9Yp) by @semmel 188 | -------------------------------------------------------------------------------- /dist/siriwave.esm.js: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | ***************************************************************************** */ 15 | 16 | function __rest(s, e) { 17 | var t = {}; 18 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 19 | t[p] = s[p]; 20 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 21 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 22 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 23 | t[p[i]] = s[p[i]]; 24 | } 25 | return t; 26 | } 27 | 28 | class ClassicCurve { 29 | constructor(ctrl, definition) { 30 | this.ATT_FACTOR = 4; 31 | this.GRAPH_X = 2; 32 | this.AMPLITUDE_FACTOR = 0.6; 33 | this.ctrl = ctrl; 34 | this.definition = definition; 35 | } 36 | globalAttFn(x) { 37 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, this.ATT_FACTOR)), this.ATT_FACTOR); 38 | } 39 | xPos(i) { 40 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 41 | } 42 | yPos(i) { 43 | return (this.AMPLITUDE_FACTOR * 44 | (this.globalAttFn(i) * 45 | (this.ctrl.heightMax * this.ctrl.amplitude) * 46 | (1 / this.definition.attenuation) * 47 | Math.sin(this.ctrl.opt.frequency * i - this.ctrl.phase))); 48 | } 49 | draw() { 50 | const { ctx } = this.ctrl; 51 | ctx.moveTo(0, 0); 52 | ctx.beginPath(); 53 | const finalColor = this.definition.color || this.ctrl.color; 54 | const colorHex = finalColor.replace(/rgb\(/g, "").replace(/\)/g, ""); 55 | ctx.strokeStyle = `rgba(${colorHex},${this.definition.opacity})`; 56 | ctx.lineWidth = this.definition.lineWidth; 57 | // Cycle the graph from -X to +X every PX_DEPTH and draw the line 58 | for (let i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth) { 59 | ctx.lineTo(this.xPos(i), this.ctrl.heightMax + this.yPos(i)); 60 | } 61 | ctx.stroke(); 62 | } 63 | static getDefinition() { 64 | return [ 65 | { 66 | attenuation: -2, 67 | lineWidth: 1, 68 | opacity: 0.1, 69 | }, 70 | { 71 | attenuation: -6, 72 | lineWidth: 1, 73 | opacity: 0.2, 74 | }, 75 | { 76 | attenuation: 4, 77 | lineWidth: 1, 78 | opacity: 0.4, 79 | }, 80 | { 81 | attenuation: 2, 82 | lineWidth: 1, 83 | opacity: 0.6, 84 | }, 85 | { 86 | attenuation: 1, 87 | lineWidth: 1.5, 88 | opacity: 1, 89 | }, 90 | ]; 91 | } 92 | } 93 | 94 | class iOS9Curve { 95 | constructor(ctrl, definition) { 96 | this.GRAPH_X = 25; 97 | this.AMPLITUDE_FACTOR = 0.8; 98 | this.SPEED_FACTOR = 1; 99 | this.DEAD_PX = 2; 100 | this.ATT_FACTOR = 4; 101 | this.DESPAWN_FACTOR = 0.02; 102 | this.DEFAULT_NOOFCURVES_RANGES = [2, 5]; 103 | this.DEFAULT_AMPLITUDE_RANGES = [0.3, 1]; 104 | this.DEFAULT_OFFSET_RANGES = [-3, 3]; 105 | this.DEFAULT_WIDTH_RANGES = [1, 3]; 106 | this.DEFAULT_SPEED_RANGES = [0.5, 1]; 107 | this.DEFAULT_DESPAWN_TIMEOUT_RANGES = [500, 2000]; 108 | this.ctrl = ctrl; 109 | this.definition = definition; 110 | this.noOfCurves = 0; 111 | this.spawnAt = 0; 112 | this.prevMaxY = 0; 113 | this.phases = []; 114 | this.offsets = []; 115 | this.speeds = []; 116 | this.finalAmplitudes = []; 117 | this.widths = []; 118 | this.amplitudes = []; 119 | this.despawnTimeouts = []; 120 | this.verses = []; 121 | } 122 | getRandomRange(e) { 123 | return e[0] + Math.random() * (e[1] - e[0]); 124 | } 125 | spawnSingle(ci) { 126 | var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; 127 | this.phases[ci] = 0; 128 | this.amplitudes[ci] = 0; 129 | this.despawnTimeouts[ci] = this.getRandomRange((_b = (_a = this.ctrl.opt.ranges) === null || _a === void 0 ? void 0 : _a.despawnTimeout) !== null && _b !== void 0 ? _b : this.DEFAULT_DESPAWN_TIMEOUT_RANGES); 130 | this.offsets[ci] = this.getRandomRange((_d = (_c = this.ctrl.opt.ranges) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : this.DEFAULT_OFFSET_RANGES); 131 | this.speeds[ci] = this.getRandomRange((_f = (_e = this.ctrl.opt.ranges) === null || _e === void 0 ? void 0 : _e.speed) !== null && _f !== void 0 ? _f : this.DEFAULT_SPEED_RANGES); 132 | this.finalAmplitudes[ci] = this.getRandomRange((_h = (_g = this.ctrl.opt.ranges) === null || _g === void 0 ? void 0 : _g.amplitude) !== null && _h !== void 0 ? _h : this.DEFAULT_AMPLITUDE_RANGES); 133 | this.widths[ci] = this.getRandomRange((_k = (_j = this.ctrl.opt.ranges) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : this.DEFAULT_WIDTH_RANGES); 134 | this.verses[ci] = this.getRandomRange([-1, 1]); 135 | } 136 | getEmptyArray(count) { 137 | return new Array(count); 138 | } 139 | spawn() { 140 | var _a, _b; 141 | this.spawnAt = Date.now(); 142 | this.noOfCurves = Math.floor(this.getRandomRange((_b = (_a = this.ctrl.opt.ranges) === null || _a === void 0 ? void 0 : _a.noOfCurves) !== null && _b !== void 0 ? _b : this.DEFAULT_NOOFCURVES_RANGES)); 143 | this.phases = this.getEmptyArray(this.noOfCurves); 144 | this.offsets = this.getEmptyArray(this.noOfCurves); 145 | this.speeds = this.getEmptyArray(this.noOfCurves); 146 | this.finalAmplitudes = this.getEmptyArray(this.noOfCurves); 147 | this.widths = this.getEmptyArray(this.noOfCurves); 148 | this.amplitudes = this.getEmptyArray(this.noOfCurves); 149 | this.despawnTimeouts = this.getEmptyArray(this.noOfCurves); 150 | this.verses = this.getEmptyArray(this.noOfCurves); 151 | for (let ci = 0; ci < this.noOfCurves; ci++) { 152 | this.spawnSingle(ci); 153 | } 154 | } 155 | globalAttFn(x) { 156 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, 2)), this.ATT_FACTOR); 157 | } 158 | sin(x, phase) { 159 | return Math.sin(x - phase); 160 | } 161 | yRelativePos(i) { 162 | let y = 0; 163 | for (let ci = 0; ci < this.noOfCurves; ci++) { 164 | // Generate a static T so that each curve is distant from each oterh 165 | let t = 4 * (-1 + (ci / (this.noOfCurves - 1)) * 2); 166 | // but add a dynamic offset 167 | t += this.offsets[ci]; 168 | const k = 1 / this.widths[ci]; 169 | const x = i * k - t; 170 | y += Math.abs(this.amplitudes[ci] * this.sin(this.verses[ci] * x, this.phases[ci]) * this.globalAttFn(x)); 171 | } 172 | // Divide for NoOfCurves so that y <= 1 173 | return y / this.noOfCurves; 174 | } 175 | yPos(i) { 176 | return (this.AMPLITUDE_FACTOR * 177 | this.ctrl.heightMax * 178 | this.ctrl.amplitude * 179 | this.yRelativePos(i) * 180 | this.globalAttFn((i / this.GRAPH_X) * 2)); 181 | } 182 | xPos(i) { 183 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 184 | } 185 | drawSupportLine() { 186 | const { ctx } = this.ctrl; 187 | const coo = [0, this.ctrl.heightMax, this.ctrl.width, 1]; 188 | const gradient = ctx.createLinearGradient.apply(ctx, coo); 189 | gradient.addColorStop(0, "transparent"); 190 | gradient.addColorStop(0.1, "rgba(255,255,255,.5)"); 191 | gradient.addColorStop(1 - 0.1 - 0.1, "rgba(255,255,255,.5)"); 192 | gradient.addColorStop(1, "transparent"); 193 | ctx.fillStyle = gradient; 194 | ctx.fillRect.apply(ctx, coo); 195 | } 196 | draw() { 197 | const { ctx } = this.ctrl; 198 | ctx.globalAlpha = 0.7; 199 | ctx.globalCompositeOperation = this.ctrl.opt.globalCompositeOperation; 200 | if (this.spawnAt === 0) { 201 | this.spawn(); 202 | } 203 | if (this.definition.supportLine) { 204 | // Draw the support line 205 | return this.drawSupportLine(); 206 | } 207 | for (let ci = 0; ci < this.noOfCurves; ci++) { 208 | if (this.spawnAt + this.despawnTimeouts[ci] <= Date.now()) { 209 | this.amplitudes[ci] -= this.DESPAWN_FACTOR; 210 | } 211 | else { 212 | this.amplitudes[ci] += this.DESPAWN_FACTOR; 213 | } 214 | this.amplitudes[ci] = Math.min(Math.max(this.amplitudes[ci], 0), this.finalAmplitudes[ci]); 215 | this.phases[ci] = (this.phases[ci] + this.ctrl.speed * this.speeds[ci] * this.SPEED_FACTOR) % (2 * Math.PI); 216 | } 217 | let maxY = -Infinity; 218 | // Write two opposite waves 219 | for (const sign of [1, -1]) { 220 | ctx.beginPath(); 221 | for (let i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth) { 222 | const x = this.xPos(i); 223 | const y = this.yPos(i); 224 | ctx.lineTo(x, this.ctrl.heightMax - sign * y); 225 | maxY = Math.max(maxY, y); 226 | } 227 | ctx.closePath(); 228 | ctx.fillStyle = `rgba(${this.definition.color}, 1)`; 229 | ctx.strokeStyle = `rgba(${this.definition.color}, 1)`; 230 | ctx.fill(); 231 | } 232 | if (maxY < this.DEAD_PX && this.prevMaxY > maxY) { 233 | this.spawnAt = 0; 234 | } 235 | this.prevMaxY = maxY; 236 | return null; 237 | } 238 | static getDefinition() { 239 | return [ 240 | { 241 | color: "255,255,255", 242 | supportLine: true, 243 | }, 244 | { 245 | // blue 246 | color: "15, 82, 169", 247 | }, 248 | { 249 | // red 250 | color: "173, 57, 76", 251 | }, 252 | { 253 | // green 254 | color: "48, 220, 155", 255 | }, 256 | ]; 257 | } 258 | } 259 | 260 | class SiriWave { 261 | constructor(_a) { 262 | var { container } = _a, rest = __rest(_a, ["container"]); 263 | // Phase of the wave (passed to Math.sin function) 264 | this.phase = 0; 265 | // Boolean value indicating the the animation is running 266 | this.run = false; 267 | // Curves objects to animate 268 | this.curves = []; 269 | const csStyle = window.getComputedStyle(container); 270 | this.opt = Object.assign({ container, style: "ios", ratio: window.devicePixelRatio || 1, speed: 0.2, amplitude: 1, frequency: 6, color: "#fff", cover: false, width: parseInt(csStyle.width.replace("px", ""), 10), height: parseInt(csStyle.height.replace("px", ""), 10), autostart: true, pixelDepth: 0.02, lerpSpeed: 0.1, globalCompositeOperation: "lighter" }, rest); 271 | /** 272 | * Actual speed of the animation. Is not safe to change this value directly, use `setSpeed` instead. 273 | */ 274 | this.speed = Number(this.opt.speed); 275 | /** 276 | * Actual amplitude of the animation. Is not safe to change this value directly, use `setAmplitude` instead. 277 | */ 278 | this.amplitude = Number(this.opt.amplitude); 279 | /** 280 | * Width of the canvas multiplied by pixel ratio 281 | */ 282 | this.width = Number(this.opt.ratio * this.opt.width); 283 | /** 284 | * Height of the canvas multiplied by pixel ratio 285 | */ 286 | this.height = Number(this.opt.ratio * this.opt.height); 287 | /** 288 | * Maximum height for a single wave 289 | */ 290 | this.heightMax = Number(this.height / 2) - 6; 291 | /** 292 | * Color of the wave (used in Classic iOS) 293 | */ 294 | this.color = `rgb(${this.hex2rgb(this.opt.color)})`; 295 | /** 296 | * An object containing controller variables that need to be interpolated 297 | * to an another value before to be actually changed 298 | */ 299 | this.interpolation = { 300 | speed: this.speed, 301 | amplitude: this.amplitude, 302 | }; 303 | /** 304 | * Canvas DOM Element where curves will be drawn 305 | */ 306 | this.canvas = document.createElement("canvas"); 307 | /** 308 | * 2D Context from Canvas 309 | */ 310 | const ctx = this.canvas.getContext("2d"); 311 | if (ctx === null) { 312 | throw new Error("Unable to create 2D Context"); 313 | } 314 | this.ctx = ctx; 315 | // Set dimensions 316 | this.canvas.width = this.width; 317 | this.canvas.height = this.height; 318 | // By covering, we ensure the canvas is in the same size of the parent 319 | if (this.opt.cover === true) { 320 | this.canvas.style.width = this.canvas.style.height = "100%"; 321 | } 322 | else { 323 | this.canvas.style.width = `${this.width / this.opt.ratio}px`; 324 | this.canvas.style.height = `${this.height / this.opt.ratio}px`; 325 | } 326 | // Instantiate all curves based on the style 327 | switch (this.opt.style) { 328 | case "ios9": 329 | this.curves = (this.opt.curveDefinition || iOS9Curve.getDefinition()).map((def) => new iOS9Curve(this, def)); 330 | break; 331 | case "ios": 332 | default: 333 | this.curves = (this.opt.curveDefinition || ClassicCurve.getDefinition()).map((def) => new ClassicCurve(this, def)); 334 | break; 335 | } 336 | // Attach to the container 337 | this.opt.container.appendChild(this.canvas); 338 | // Start the animation 339 | if (this.opt.autostart) { 340 | this.start(); 341 | } 342 | } 343 | /** 344 | * Convert an HEX color to RGB 345 | */ 346 | hex2rgb(hex) { 347 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 348 | hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); 349 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 350 | return result 351 | ? `${parseInt(result[1], 16).toString()},${parseInt(result[2], 16).toString()},${parseInt(result[3], 16).toString()}` 352 | : null; 353 | } 354 | intLerp(v0, v1, t) { 355 | return v0 * (1 - t) + v1 * t; 356 | } 357 | /** 358 | * Interpolate a property to the value found in this.interpolation 359 | */ 360 | lerp(propertyStr) { 361 | const prop = this.interpolation[propertyStr]; 362 | if (prop !== null) { 363 | this[propertyStr] = this.intLerp(this[propertyStr], prop, this.opt.lerpSpeed); 364 | if (this[propertyStr] - prop === 0) { 365 | this.interpolation[propertyStr] = null; 366 | } 367 | } 368 | return this[propertyStr]; 369 | } 370 | /** 371 | * Clear the canvas 372 | */ 373 | clear() { 374 | this.ctx.globalCompositeOperation = "destination-out"; 375 | this.ctx.fillRect(0, 0, this.width, this.height); 376 | this.ctx.globalCompositeOperation = "source-over"; 377 | } 378 | /** 379 | * Draw all curves 380 | */ 381 | draw() { 382 | this.curves.forEach((curve) => curve.draw()); 383 | } 384 | /** 385 | * Clear the space, interpolate values, calculate new steps and draws 386 | * @returns 387 | */ 388 | startDrawCycle() { 389 | this.clear(); 390 | // Interpolate values 391 | this.lerp("amplitude"); 392 | this.lerp("speed"); 393 | this.draw(); 394 | this.phase = (this.phase + (Math.PI / 2) * this.speed) % (2 * Math.PI); 395 | if (window.requestAnimationFrame) { 396 | this.animationFrameId = window.requestAnimationFrame(this.startDrawCycle.bind(this)); 397 | } 398 | else { 399 | this.timeoutId = setTimeout(this.startDrawCycle.bind(this), 20); 400 | } 401 | } 402 | /* API */ 403 | /** 404 | * Start the animation 405 | */ 406 | start() { 407 | if (!this.canvas) { 408 | throw new Error("This instance of SiriWave has been disposed, please create a new instance"); 409 | } 410 | this.phase = 0; 411 | // Ensure we don't re-launch the draw cycle 412 | if (!this.run) { 413 | this.run = true; 414 | this.startDrawCycle(); 415 | } 416 | } 417 | /** 418 | * Stop the animation 419 | */ 420 | stop() { 421 | this.phase = 0; 422 | this.run = false; 423 | // Clear old draw cycle on stop 424 | if (this.animationFrameId) { 425 | window.cancelAnimationFrame(this.animationFrameId); 426 | } 427 | if (this.timeoutId) { 428 | clearTimeout(this.timeoutId); 429 | } 430 | } 431 | /** 432 | * Dispose 433 | */ 434 | dispose() { 435 | this.stop(); 436 | if (this.canvas) { 437 | this.canvas.remove(); 438 | this.canvas = null; 439 | } 440 | } 441 | /** 442 | * Set a new value for a property (interpolated) 443 | */ 444 | set(propertyStr, value) { 445 | this.interpolation[propertyStr] = value; 446 | } 447 | /** 448 | * Set a new value for the speed property (interpolated) 449 | */ 450 | setSpeed(value) { 451 | this.set("speed", value); 452 | } 453 | /** 454 | * Set a new value for the amplitude property (interpolated) 455 | */ 456 | setAmplitude(value) { 457 | this.set("amplitude", value); 458 | } 459 | } 460 | 461 | export { SiriWave as default }; 462 | -------------------------------------------------------------------------------- /dist/siriwave.esm.min.js: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | ***************************************************************************** */ 15 | class t{constructor(t,i){this.ATT_FACTOR=4,this.GRAPH_X=2,this.AMPLITUDE_FACTOR=.6,this.ctrl=t,this.definition=i}globalAttFn(t){return Math.pow(this.ATT_FACTOR/(this.ATT_FACTOR+Math.pow(t,this.ATT_FACTOR)),this.ATT_FACTOR)}xPos(t){return this.ctrl.width*((t+this.GRAPH_X)/(2*this.GRAPH_X))}yPos(t){return this.AMPLITUDE_FACTOR*(this.globalAttFn(t)*(this.ctrl.heightMax*this.ctrl.amplitude)*(1/this.definition.attenuation)*Math.sin(this.ctrl.opt.frequency*t-this.ctrl.phase))}draw(){const{ctx:t}=this.ctrl;t.moveTo(0,0),t.beginPath();const i=(this.definition.color||this.ctrl.color).replace(/rgb\(/g,"").replace(/\)/g,"");t.strokeStyle=`rgba(${i},${this.definition.opacity})`,t.lineWidth=this.definition.lineWidth;for(let i=-this.GRAPH_X;i<=this.GRAPH_X;i+=this.ctrl.opt.pixelDepth)t.lineTo(this.xPos(i),this.ctrl.heightMax+this.yPos(i));t.stroke()}static getDefinition(){return[{attenuation:-2,lineWidth:1,opacity:.1},{attenuation:-6,lineWidth:1,opacity:.2},{attenuation:4,lineWidth:1,opacity:.4},{attenuation:2,lineWidth:1,opacity:.6},{attenuation:1,lineWidth:1.5,opacity:1}]}}class i{constructor(t,i){this.GRAPH_X=25,this.AMPLITUDE_FACTOR=.8,this.SPEED_FACTOR=1,this.DEAD_PX=2,this.ATT_FACTOR=4,this.DESPAWN_FACTOR=.02,this.DEFAULT_NOOFCURVES_RANGES=[2,5],this.DEFAULT_AMPLITUDE_RANGES=[.3,1],this.DEFAULT_OFFSET_RANGES=[-3,3],this.DEFAULT_WIDTH_RANGES=[1,3],this.DEFAULT_SPEED_RANGES=[.5,1],this.DEFAULT_DESPAWN_TIMEOUT_RANGES=[500,2e3],this.ctrl=t,this.definition=i,this.noOfCurves=0,this.spawnAt=0,this.prevMaxY=0,this.phases=[],this.offsets=[],this.speeds=[],this.finalAmplitudes=[],this.widths=[],this.amplitudes=[],this.despawnTimeouts=[],this.verses=[]}getRandomRange(t){return t[0]+Math.random()*(t[1]-t[0])}spawnSingle(t){var i,s,e,h,o,a,r,n,l,p;this.phases[t]=0,this.amplitudes[t]=0,this.despawnTimeouts[t]=this.getRandomRange(null!==(s=null===(i=this.ctrl.opt.ranges)||void 0===i?void 0:i.despawnTimeout)&&void 0!==s?s:this.DEFAULT_DESPAWN_TIMEOUT_RANGES),this.offsets[t]=this.getRandomRange(null!==(h=null===(e=this.ctrl.opt.ranges)||void 0===e?void 0:e.offset)&&void 0!==h?h:this.DEFAULT_OFFSET_RANGES),this.speeds[t]=this.getRandomRange(null!==(a=null===(o=this.ctrl.opt.ranges)||void 0===o?void 0:o.speed)&&void 0!==a?a:this.DEFAULT_SPEED_RANGES),this.finalAmplitudes[t]=this.getRandomRange(null!==(n=null===(r=this.ctrl.opt.ranges)||void 0===r?void 0:r.amplitude)&&void 0!==n?n:this.DEFAULT_AMPLITUDE_RANGES),this.widths[t]=this.getRandomRange(null!==(p=null===(l=this.ctrl.opt.ranges)||void 0===l?void 0:l.width)&&void 0!==p?p:this.DEFAULT_WIDTH_RANGES),this.verses[t]=this.getRandomRange([-1,1])}getEmptyArray(t){return new Array(t)}spawn(){var t,i;this.spawnAt=Date.now(),this.noOfCurves=Math.floor(this.getRandomRange(null!==(i=null===(t=this.ctrl.opt.ranges)||void 0===t?void 0:t.noOfCurves)&&void 0!==i?i:this.DEFAULT_NOOFCURVES_RANGES)),this.phases=this.getEmptyArray(this.noOfCurves),this.offsets=this.getEmptyArray(this.noOfCurves),this.speeds=this.getEmptyArray(this.noOfCurves),this.finalAmplitudes=this.getEmptyArray(this.noOfCurves),this.widths=this.getEmptyArray(this.noOfCurves),this.amplitudes=this.getEmptyArray(this.noOfCurves),this.despawnTimeouts=this.getEmptyArray(this.noOfCurves),this.verses=this.getEmptyArray(this.noOfCurves);for(let t=0;ti&&(this.spawnAt=0),this.prevMaxY=i,null}static getDefinition(){return[{color:"255,255,255",supportLine:!0},{color:"15, 82, 169"},{color:"173, 57, 76"},{color:"48, 220, 155"}]}}class s{constructor(s){var{container:e}=s,h=function(t,i){var s={};for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&i.indexOf(e)<0&&(s[e]=t[e]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var h=0;for(e=Object.getOwnPropertySymbols(t);hnew i(this,t)));else this.curves=(this.opt.curveDefinition||t.getDefinition()).map((i=>new t(this,i)));this.opt.container.appendChild(this.canvas),this.opt.autostart&&this.start()}hex2rgb(t){t=t.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,((t,i,s,e)=>i+i+s+s+e+e));const i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return i?`${parseInt(i[1],16).toString()},${parseInt(i[2],16).toString()},${parseInt(i[3],16).toString()}`:null}intLerp(t,i,s){return t*(1-s)+i*s}lerp(t){const i=this.interpolation[t];return null!==i&&(this[t]=this.intLerp(this[t],i,this.opt.lerpSpeed),this[t]-i==0&&(this.interpolation[t]=null)),this[t]}clear(){this.ctx.globalCompositeOperation="destination-out",this.ctx.fillRect(0,0,this.width,this.height),this.ctx.globalCompositeOperation="source-over"}draw(){this.curves.forEach((t=>t.draw()))}startDrawCycle(){this.clear(),this.lerp("amplitude"),this.lerp("speed"),this.draw(),this.phase=(this.phase+Math.PI/2*this.speed)%(2*Math.PI),window.requestAnimationFrame?this.animationFrameId=window.requestAnimationFrame(this.startDrawCycle.bind(this)):this.timeoutId=setTimeout(this.startDrawCycle.bind(this),20)}start(){if(!this.canvas)throw new Error("This instance of SiriWave has been disposed, please create a new instance");this.phase=0,this.run||(this.run=!0,this.startDrawCycle())}stop(){this.phase=0,this.run=!1,this.animationFrameId&&window.cancelAnimationFrame(this.animationFrameId),this.timeoutId&&clearTimeout(this.timeoutId)}dispose(){this.stop(),this.canvas&&(this.canvas.remove(),this.canvas=null)}set(t,i){this.interpolation[t]=i}setSpeed(t){this.set("speed",t)}setAmplitude(t){this.set("amplitude",t)}}export{s as default}; 16 | -------------------------------------------------------------------------------- /dist/siriwave.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SiriWave = factory()); 5 | })(this, (function () { 'use strict'; 6 | 7 | /*! ***************************************************************************** 8 | Copyright (c) Microsoft Corporation. 9 | 10 | Permission to use, copy, modify, and/or distribute this software for any 11 | purpose with or without fee is hereby granted. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 14 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 16 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 17 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 18 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 19 | PERFORMANCE OF THIS SOFTWARE. 20 | ***************************************************************************** */ 21 | 22 | var __assign = function() { 23 | __assign = Object.assign || function __assign(t) { 24 | for (var s, i = 1, n = arguments.length; i < n; i++) { 25 | s = arguments[i]; 26 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 27 | } 28 | return t; 29 | }; 30 | return __assign.apply(this, arguments); 31 | }; 32 | 33 | function __rest(s, e) { 34 | var t = {}; 35 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) 36 | t[p] = s[p]; 37 | if (s != null && typeof Object.getOwnPropertySymbols === "function") 38 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { 39 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) 40 | t[p[i]] = s[p[i]]; 41 | } 42 | return t; 43 | } 44 | 45 | var ClassicCurve = /** @class */ (function () { 46 | function ClassicCurve(ctrl, definition) { 47 | this.ATT_FACTOR = 4; 48 | this.GRAPH_X = 2; 49 | this.AMPLITUDE_FACTOR = 0.6; 50 | this.ctrl = ctrl; 51 | this.definition = definition; 52 | } 53 | ClassicCurve.prototype.globalAttFn = function (x) { 54 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, this.ATT_FACTOR)), this.ATT_FACTOR); 55 | }; 56 | ClassicCurve.prototype.xPos = function (i) { 57 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 58 | }; 59 | ClassicCurve.prototype.yPos = function (i) { 60 | return (this.AMPLITUDE_FACTOR * 61 | (this.globalAttFn(i) * 62 | (this.ctrl.heightMax * this.ctrl.amplitude) * 63 | (1 / this.definition.attenuation) * 64 | Math.sin(this.ctrl.opt.frequency * i - this.ctrl.phase))); 65 | }; 66 | ClassicCurve.prototype.draw = function () { 67 | var ctx = this.ctrl.ctx; 68 | ctx.moveTo(0, 0); 69 | ctx.beginPath(); 70 | var finalColor = this.definition.color || this.ctrl.color; 71 | var colorHex = finalColor.replace(/rgb\(/g, "").replace(/\)/g, ""); 72 | ctx.strokeStyle = "rgba(".concat(colorHex, ",").concat(this.definition.opacity, ")"); 73 | ctx.lineWidth = this.definition.lineWidth; 74 | // Cycle the graph from -X to +X every PX_DEPTH and draw the line 75 | for (var i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth) { 76 | ctx.lineTo(this.xPos(i), this.ctrl.heightMax + this.yPos(i)); 77 | } 78 | ctx.stroke(); 79 | }; 80 | ClassicCurve.getDefinition = function () { 81 | return [ 82 | { 83 | attenuation: -2, 84 | lineWidth: 1, 85 | opacity: 0.1, 86 | }, 87 | { 88 | attenuation: -6, 89 | lineWidth: 1, 90 | opacity: 0.2, 91 | }, 92 | { 93 | attenuation: 4, 94 | lineWidth: 1, 95 | opacity: 0.4, 96 | }, 97 | { 98 | attenuation: 2, 99 | lineWidth: 1, 100 | opacity: 0.6, 101 | }, 102 | { 103 | attenuation: 1, 104 | lineWidth: 1.5, 105 | opacity: 1, 106 | }, 107 | ]; 108 | }; 109 | return ClassicCurve; 110 | }()); 111 | 112 | var iOS9Curve = /** @class */ (function () { 113 | function iOS9Curve(ctrl, definition) { 114 | this.GRAPH_X = 25; 115 | this.AMPLITUDE_FACTOR = 0.8; 116 | this.SPEED_FACTOR = 1; 117 | this.DEAD_PX = 2; 118 | this.ATT_FACTOR = 4; 119 | this.DESPAWN_FACTOR = 0.02; 120 | this.DEFAULT_NOOFCURVES_RANGES = [2, 5]; 121 | this.DEFAULT_AMPLITUDE_RANGES = [0.3, 1]; 122 | this.DEFAULT_OFFSET_RANGES = [-3, 3]; 123 | this.DEFAULT_WIDTH_RANGES = [1, 3]; 124 | this.DEFAULT_SPEED_RANGES = [0.5, 1]; 125 | this.DEFAULT_DESPAWN_TIMEOUT_RANGES = [500, 2000]; 126 | this.ctrl = ctrl; 127 | this.definition = definition; 128 | this.noOfCurves = 0; 129 | this.spawnAt = 0; 130 | this.prevMaxY = 0; 131 | this.phases = []; 132 | this.offsets = []; 133 | this.speeds = []; 134 | this.finalAmplitudes = []; 135 | this.widths = []; 136 | this.amplitudes = []; 137 | this.despawnTimeouts = []; 138 | this.verses = []; 139 | } 140 | iOS9Curve.prototype.getRandomRange = function (e) { 141 | return e[0] + Math.random() * (e[1] - e[0]); 142 | }; 143 | iOS9Curve.prototype.spawnSingle = function (ci) { 144 | var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; 145 | this.phases[ci] = 0; 146 | this.amplitudes[ci] = 0; 147 | this.despawnTimeouts[ci] = this.getRandomRange((_b = (_a = this.ctrl.opt.ranges) === null || _a === void 0 ? void 0 : _a.despawnTimeout) !== null && _b !== void 0 ? _b : this.DEFAULT_DESPAWN_TIMEOUT_RANGES); 148 | this.offsets[ci] = this.getRandomRange((_d = (_c = this.ctrl.opt.ranges) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : this.DEFAULT_OFFSET_RANGES); 149 | this.speeds[ci] = this.getRandomRange((_f = (_e = this.ctrl.opt.ranges) === null || _e === void 0 ? void 0 : _e.speed) !== null && _f !== void 0 ? _f : this.DEFAULT_SPEED_RANGES); 150 | this.finalAmplitudes[ci] = this.getRandomRange((_h = (_g = this.ctrl.opt.ranges) === null || _g === void 0 ? void 0 : _g.amplitude) !== null && _h !== void 0 ? _h : this.DEFAULT_AMPLITUDE_RANGES); 151 | this.widths[ci] = this.getRandomRange((_k = (_j = this.ctrl.opt.ranges) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : this.DEFAULT_WIDTH_RANGES); 152 | this.verses[ci] = this.getRandomRange([-1, 1]); 153 | }; 154 | iOS9Curve.prototype.getEmptyArray = function (count) { 155 | return new Array(count); 156 | }; 157 | iOS9Curve.prototype.spawn = function () { 158 | var _a, _b; 159 | this.spawnAt = Date.now(); 160 | this.noOfCurves = Math.floor(this.getRandomRange((_b = (_a = this.ctrl.opt.ranges) === null || _a === void 0 ? void 0 : _a.noOfCurves) !== null && _b !== void 0 ? _b : this.DEFAULT_NOOFCURVES_RANGES)); 161 | this.phases = this.getEmptyArray(this.noOfCurves); 162 | this.offsets = this.getEmptyArray(this.noOfCurves); 163 | this.speeds = this.getEmptyArray(this.noOfCurves); 164 | this.finalAmplitudes = this.getEmptyArray(this.noOfCurves); 165 | this.widths = this.getEmptyArray(this.noOfCurves); 166 | this.amplitudes = this.getEmptyArray(this.noOfCurves); 167 | this.despawnTimeouts = this.getEmptyArray(this.noOfCurves); 168 | this.verses = this.getEmptyArray(this.noOfCurves); 169 | for (var ci = 0; ci < this.noOfCurves; ci++) { 170 | this.spawnSingle(ci); 171 | } 172 | }; 173 | iOS9Curve.prototype.globalAttFn = function (x) { 174 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, 2)), this.ATT_FACTOR); 175 | }; 176 | iOS9Curve.prototype.sin = function (x, phase) { 177 | return Math.sin(x - phase); 178 | }; 179 | iOS9Curve.prototype.yRelativePos = function (i) { 180 | var y = 0; 181 | for (var ci = 0; ci < this.noOfCurves; ci++) { 182 | // Generate a static T so that each curve is distant from each oterh 183 | var t = 4 * (-1 + (ci / (this.noOfCurves - 1)) * 2); 184 | // but add a dynamic offset 185 | t += this.offsets[ci]; 186 | var k = 1 / this.widths[ci]; 187 | var x = i * k - t; 188 | y += Math.abs(this.amplitudes[ci] * this.sin(this.verses[ci] * x, this.phases[ci]) * this.globalAttFn(x)); 189 | } 190 | // Divide for NoOfCurves so that y <= 1 191 | return y / this.noOfCurves; 192 | }; 193 | iOS9Curve.prototype.yPos = function (i) { 194 | return (this.AMPLITUDE_FACTOR * 195 | this.ctrl.heightMax * 196 | this.ctrl.amplitude * 197 | this.yRelativePos(i) * 198 | this.globalAttFn((i / this.GRAPH_X) * 2)); 199 | }; 200 | iOS9Curve.prototype.xPos = function (i) { 201 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 202 | }; 203 | iOS9Curve.prototype.drawSupportLine = function () { 204 | var ctx = this.ctrl.ctx; 205 | var coo = [0, this.ctrl.heightMax, this.ctrl.width, 1]; 206 | var gradient = ctx.createLinearGradient.apply(ctx, coo); 207 | gradient.addColorStop(0, "transparent"); 208 | gradient.addColorStop(0.1, "rgba(255,255,255,.5)"); 209 | gradient.addColorStop(1 - 0.1 - 0.1, "rgba(255,255,255,.5)"); 210 | gradient.addColorStop(1, "transparent"); 211 | ctx.fillStyle = gradient; 212 | ctx.fillRect.apply(ctx, coo); 213 | }; 214 | iOS9Curve.prototype.draw = function () { 215 | var ctx = this.ctrl.ctx; 216 | ctx.globalAlpha = 0.7; 217 | ctx.globalCompositeOperation = this.ctrl.opt.globalCompositeOperation; 218 | if (this.spawnAt === 0) { 219 | this.spawn(); 220 | } 221 | if (this.definition.supportLine) { 222 | // Draw the support line 223 | return this.drawSupportLine(); 224 | } 225 | for (var ci = 0; ci < this.noOfCurves; ci++) { 226 | if (this.spawnAt + this.despawnTimeouts[ci] <= Date.now()) { 227 | this.amplitudes[ci] -= this.DESPAWN_FACTOR; 228 | } 229 | else { 230 | this.amplitudes[ci] += this.DESPAWN_FACTOR; 231 | } 232 | this.amplitudes[ci] = Math.min(Math.max(this.amplitudes[ci], 0), this.finalAmplitudes[ci]); 233 | this.phases[ci] = (this.phases[ci] + this.ctrl.speed * this.speeds[ci] * this.SPEED_FACTOR) % (2 * Math.PI); 234 | } 235 | var maxY = -Infinity; 236 | // Write two opposite waves 237 | for (var _i = 0, _a = [1, -1]; _i < _a.length; _i++) { 238 | var sign = _a[_i]; 239 | ctx.beginPath(); 240 | for (var i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth) { 241 | var x = this.xPos(i); 242 | var y = this.yPos(i); 243 | ctx.lineTo(x, this.ctrl.heightMax - sign * y); 244 | maxY = Math.max(maxY, y); 245 | } 246 | ctx.closePath(); 247 | ctx.fillStyle = "rgba(".concat(this.definition.color, ", 1)"); 248 | ctx.strokeStyle = "rgba(".concat(this.definition.color, ", 1)"); 249 | ctx.fill(); 250 | } 251 | if (maxY < this.DEAD_PX && this.prevMaxY > maxY) { 252 | this.spawnAt = 0; 253 | } 254 | this.prevMaxY = maxY; 255 | return null; 256 | }; 257 | iOS9Curve.getDefinition = function () { 258 | return [ 259 | { 260 | color: "255,255,255", 261 | supportLine: true, 262 | }, 263 | { 264 | // blue 265 | color: "15, 82, 169", 266 | }, 267 | { 268 | // red 269 | color: "173, 57, 76", 270 | }, 271 | { 272 | // green 273 | color: "48, 220, 155", 274 | }, 275 | ]; 276 | }; 277 | return iOS9Curve; 278 | }()); 279 | 280 | var SiriWave = /** @class */ (function () { 281 | function SiriWave(_a) { 282 | var _this = this; 283 | var container = _a.container, rest = __rest(_a, ["container"]); 284 | // Phase of the wave (passed to Math.sin function) 285 | this.phase = 0; 286 | // Boolean value indicating the the animation is running 287 | this.run = false; 288 | // Curves objects to animate 289 | this.curves = []; 290 | var csStyle = window.getComputedStyle(container); 291 | this.opt = __assign({ container: container, style: "ios", ratio: window.devicePixelRatio || 1, speed: 0.2, amplitude: 1, frequency: 6, color: "#fff", cover: false, width: parseInt(csStyle.width.replace("px", ""), 10), height: parseInt(csStyle.height.replace("px", ""), 10), autostart: true, pixelDepth: 0.02, lerpSpeed: 0.1, globalCompositeOperation: "lighter" }, rest); 292 | /** 293 | * Actual speed of the animation. Is not safe to change this value directly, use `setSpeed` instead. 294 | */ 295 | this.speed = Number(this.opt.speed); 296 | /** 297 | * Actual amplitude of the animation. Is not safe to change this value directly, use `setAmplitude` instead. 298 | */ 299 | this.amplitude = Number(this.opt.amplitude); 300 | /** 301 | * Width of the canvas multiplied by pixel ratio 302 | */ 303 | this.width = Number(this.opt.ratio * this.opt.width); 304 | /** 305 | * Height of the canvas multiplied by pixel ratio 306 | */ 307 | this.height = Number(this.opt.ratio * this.opt.height); 308 | /** 309 | * Maximum height for a single wave 310 | */ 311 | this.heightMax = Number(this.height / 2) - 6; 312 | /** 313 | * Color of the wave (used in Classic iOS) 314 | */ 315 | this.color = "rgb(".concat(this.hex2rgb(this.opt.color), ")"); 316 | /** 317 | * An object containing controller variables that need to be interpolated 318 | * to an another value before to be actually changed 319 | */ 320 | this.interpolation = { 321 | speed: this.speed, 322 | amplitude: this.amplitude, 323 | }; 324 | /** 325 | * Canvas DOM Element where curves will be drawn 326 | */ 327 | this.canvas = document.createElement("canvas"); 328 | /** 329 | * 2D Context from Canvas 330 | */ 331 | var ctx = this.canvas.getContext("2d"); 332 | if (ctx === null) { 333 | throw new Error("Unable to create 2D Context"); 334 | } 335 | this.ctx = ctx; 336 | // Set dimensions 337 | this.canvas.width = this.width; 338 | this.canvas.height = this.height; 339 | // By covering, we ensure the canvas is in the same size of the parent 340 | if (this.opt.cover === true) { 341 | this.canvas.style.width = this.canvas.style.height = "100%"; 342 | } 343 | else { 344 | this.canvas.style.width = "".concat(this.width / this.opt.ratio, "px"); 345 | this.canvas.style.height = "".concat(this.height / this.opt.ratio, "px"); 346 | } 347 | // Instantiate all curves based on the style 348 | switch (this.opt.style) { 349 | case "ios9": 350 | this.curves = (this.opt.curveDefinition || iOS9Curve.getDefinition()).map(function (def) { return new iOS9Curve(_this, def); }); 351 | break; 352 | case "ios": 353 | default: 354 | this.curves = (this.opt.curveDefinition || ClassicCurve.getDefinition()).map(function (def) { return new ClassicCurve(_this, def); }); 355 | break; 356 | } 357 | // Attach to the container 358 | this.opt.container.appendChild(this.canvas); 359 | // Start the animation 360 | if (this.opt.autostart) { 361 | this.start(); 362 | } 363 | } 364 | /** 365 | * Convert an HEX color to RGB 366 | */ 367 | SiriWave.prototype.hex2rgb = function (hex) { 368 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 369 | hex = hex.replace(shorthandRegex, function (m, r, g, b) { return r + r + g + g + b + b; }); 370 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 371 | return result 372 | ? "".concat(parseInt(result[1], 16).toString(), ",").concat(parseInt(result[2], 16).toString(), ",").concat(parseInt(result[3], 16).toString()) 373 | : null; 374 | }; 375 | SiriWave.prototype.intLerp = function (v0, v1, t) { 376 | return v0 * (1 - t) + v1 * t; 377 | }; 378 | /** 379 | * Interpolate a property to the value found in this.interpolation 380 | */ 381 | SiriWave.prototype.lerp = function (propertyStr) { 382 | var prop = this.interpolation[propertyStr]; 383 | if (prop !== null) { 384 | this[propertyStr] = this.intLerp(this[propertyStr], prop, this.opt.lerpSpeed); 385 | if (this[propertyStr] - prop === 0) { 386 | this.interpolation[propertyStr] = null; 387 | } 388 | } 389 | return this[propertyStr]; 390 | }; 391 | /** 392 | * Clear the canvas 393 | */ 394 | SiriWave.prototype.clear = function () { 395 | this.ctx.globalCompositeOperation = "destination-out"; 396 | this.ctx.fillRect(0, 0, this.width, this.height); 397 | this.ctx.globalCompositeOperation = "source-over"; 398 | }; 399 | /** 400 | * Draw all curves 401 | */ 402 | SiriWave.prototype.draw = function () { 403 | this.curves.forEach(function (curve) { return curve.draw(); }); 404 | }; 405 | /** 406 | * Clear the space, interpolate values, calculate new steps and draws 407 | * @returns 408 | */ 409 | SiriWave.prototype.startDrawCycle = function () { 410 | this.clear(); 411 | // Interpolate values 412 | this.lerp("amplitude"); 413 | this.lerp("speed"); 414 | this.draw(); 415 | this.phase = (this.phase + (Math.PI / 2) * this.speed) % (2 * Math.PI); 416 | if (window.requestAnimationFrame) { 417 | this.animationFrameId = window.requestAnimationFrame(this.startDrawCycle.bind(this)); 418 | } 419 | else { 420 | this.timeoutId = setTimeout(this.startDrawCycle.bind(this), 20); 421 | } 422 | }; 423 | /* API */ 424 | /** 425 | * Start the animation 426 | */ 427 | SiriWave.prototype.start = function () { 428 | if (!this.canvas) { 429 | throw new Error("This instance of SiriWave has been disposed, please create a new instance"); 430 | } 431 | this.phase = 0; 432 | // Ensure we don't re-launch the draw cycle 433 | if (!this.run) { 434 | this.run = true; 435 | this.startDrawCycle(); 436 | } 437 | }; 438 | /** 439 | * Stop the animation 440 | */ 441 | SiriWave.prototype.stop = function () { 442 | this.phase = 0; 443 | this.run = false; 444 | // Clear old draw cycle on stop 445 | if (this.animationFrameId) { 446 | window.cancelAnimationFrame(this.animationFrameId); 447 | } 448 | if (this.timeoutId) { 449 | clearTimeout(this.timeoutId); 450 | } 451 | }; 452 | /** 453 | * Dispose 454 | */ 455 | SiriWave.prototype.dispose = function () { 456 | this.stop(); 457 | if (this.canvas) { 458 | this.canvas.remove(); 459 | this.canvas = null; 460 | } 461 | }; 462 | /** 463 | * Set a new value for a property (interpolated) 464 | */ 465 | SiriWave.prototype.set = function (propertyStr, value) { 466 | this.interpolation[propertyStr] = value; 467 | }; 468 | /** 469 | * Set a new value for the speed property (interpolated) 470 | */ 471 | SiriWave.prototype.setSpeed = function (value) { 472 | this.set("speed", value); 473 | }; 474 | /** 475 | * Set a new value for the amplitude property (interpolated) 476 | */ 477 | SiriWave.prototype.setAmplitude = function (value) { 478 | this.set("amplitude", value); 479 | }; 480 | return SiriWave; 481 | }()); 482 | 483 | return SiriWave; 484 | 485 | })); 486 | -------------------------------------------------------------------------------- /dist/siriwave.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).SiriWave=i()}(this,(function(){"use strict"; 2 | /*! ***************************************************************************** 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | ***************************************************************************** */var t=function(){return t=Object.assign||function(t){for(var i,s=1,e=arguments.length;ss&&(this.spawnAt=0),this.prevMaxY=s,null},t.getDefinition=function(){return[{color:"255,255,255",supportLine:!0},{color:"15, 82, 169"},{color:"173, 57, 76"},{color:"48, 220, 155"}]},t}();return function(){function e(e){var o=this,n=e.container,h=function(t,i){var s={};for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&i.indexOf(e)<0&&(s[e]=t[e]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(e=Object.getOwnPropertySymbols(t);o void; 42 | } 43 | export default class SiriWave { 44 | opt: Options; 45 | phase: number; 46 | run: boolean; 47 | curves: ICurve[]; 48 | speed: number; 49 | amplitude: number; 50 | width: number; 51 | height: number; 52 | heightMax: number; 53 | color: string; 54 | interpolation: { 55 | speed: number | null; 56 | amplitude: number | null; 57 | }; 58 | canvas: HTMLCanvasElement | null; 59 | ctx: CanvasRenderingContext2D; 60 | animationFrameId: number | undefined; 61 | timeoutId: ReturnType | undefined; 62 | constructor({ container, ...rest }: Options); 63 | /** 64 | * Convert an HEX color to RGB 65 | */ 66 | private hex2rgb; 67 | private intLerp; 68 | /** 69 | * Interpolate a property to the value found in this.interpolation 70 | */ 71 | private lerp; 72 | /** 73 | * Clear the canvas 74 | */ 75 | private clear; 76 | /** 77 | * Draw all curves 78 | */ 79 | private draw; 80 | /** 81 | * Clear the space, interpolate values, calculate new steps and draws 82 | * @returns 83 | */ 84 | private startDrawCycle; 85 | /** 86 | * Start the animation 87 | */ 88 | start(): void; 89 | /** 90 | * Stop the animation 91 | */ 92 | stop(): void; 93 | /** 94 | * Dispose 95 | */ 96 | dispose(): void; 97 | /** 98 | * Set a new value for a property (interpolated) 99 | */ 100 | set(propertyStr: "amplitude" | "speed", value: number): void; 101 | /** 102 | * Set a new value for the speed property (interpolated) 103 | */ 104 | setSpeed(value: number): void; 105 | /** 106 | * Set a new value for the amplitude property (interpolated) 107 | */ 108 | setAmplitude(value: number): void; 109 | } 110 | export {}; 111 | -------------------------------------------------------------------------------- /dist/types/ios9-curve.d.ts: -------------------------------------------------------------------------------- 1 | import SiriWave, { ICurve, IiOS9CurveDefinition } from "./index"; 2 | export declare class iOS9Curve implements ICurve { 3 | ctrl: SiriWave; 4 | definition: IiOS9CurveDefinition; 5 | spawnAt: number; 6 | noOfCurves: number; 7 | prevMaxY: number; 8 | phases: number[]; 9 | amplitudes: number[]; 10 | despawnTimeouts: number[]; 11 | offsets: number[]; 12 | speeds: number[]; 13 | finalAmplitudes: number[]; 14 | widths: number[]; 15 | verses: number[]; 16 | GRAPH_X: number; 17 | AMPLITUDE_FACTOR: number; 18 | SPEED_FACTOR: number; 19 | DEAD_PX: number; 20 | ATT_FACTOR: number; 21 | DESPAWN_FACTOR: number; 22 | DEFAULT_NOOFCURVES_RANGES: [number, number]; 23 | DEFAULT_AMPLITUDE_RANGES: [number, number]; 24 | DEFAULT_OFFSET_RANGES: [number, number]; 25 | DEFAULT_WIDTH_RANGES: [number, number]; 26 | DEFAULT_SPEED_RANGES: [number, number]; 27 | DEFAULT_DESPAWN_TIMEOUT_RANGES: [number, number]; 28 | constructor(ctrl: SiriWave, definition: IiOS9CurveDefinition); 29 | private getRandomRange; 30 | private spawnSingle; 31 | private getEmptyArray; 32 | private spawn; 33 | private globalAttFn; 34 | private sin; 35 | private yRelativePos; 36 | private yPos; 37 | private xPos; 38 | private drawSupportLine; 39 | draw(): void | null; 40 | static getDefinition(): IiOS9CurveDefinition[]; 41 | } 42 | -------------------------------------------------------------------------------- /etc/classic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kopiro/siriwave/744cb00235b7900733efff25dc73b4d1ab77c92d/etc/classic.gif -------------------------------------------------------------------------------- /etc/dat.gui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | ! function (e, t) { 14 | "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t(e.dat = {}) 15 | }(this, function (e) { 16 | "use strict"; 17 | 18 | function t(e, t) { 19 | var n = e.__state.conversionName.toString(), 20 | o = Math.round(e.r), 21 | i = Math.round(e.g), 22 | r = Math.round(e.b), 23 | s = e.a, 24 | a = Math.round(e.h), 25 | l = e.s.toFixed(1), 26 | d = e.v.toFixed(1); 27 | if (t || "THREE_CHAR_HEX" === n || "SIX_CHAR_HEX" === n) { 28 | for (var c = e.hex.toString(16); c.length < 6;) c = "0" + c; 29 | return "#" + c 30 | } 31 | return "CSS_RGB" === n ? "rgb(" + o + "," + i + "," + r + ")" : "CSS_RGBA" === n ? "rgba(" + o + "," + i + "," + r + "," + s + ")" : "HEX" === n ? "0x" + e.hex.toString(16) : "RGB_ARRAY" === n ? "[" + o + "," + i + "," + r + "]" : "RGBA_ARRAY" === n ? "[" + o + "," + i + "," + r + "," + s + "]" : "RGB_OBJ" === n ? "{r:" + o + ",g:" + i + ",b:" + r + "}" : "RGBA_OBJ" === n ? "{r:" + o + ",g:" + i + ",b:" + r + ",a:" + s + "}" : "HSV_OBJ" === n ? "{h:" + a + ",s:" + l + ",v:" + d + "}" : "HSVA_OBJ" === n ? "{h:" + a + ",s:" + l + ",v:" + d + ",a:" + s + "}" : "unknown format" 32 | } 33 | 34 | function n(e, t, n) { 35 | Object.defineProperty(e, t, { 36 | get: function () { 37 | return "RGB" === this.__state.space ? this.__state[t] : (I.recalculateRGB(this, t, n), this.__state[t]) 38 | }, 39 | set: function (e) { 40 | "RGB" !== this.__state.space && (I.recalculateRGB(this, t, n), this.__state.space = "RGB"), this.__state[t] = e 41 | } 42 | }) 43 | } 44 | 45 | function o(e, t) { 46 | Object.defineProperty(e, t, { 47 | get: function () { 48 | return "HSV" === this.__state.space ? this.__state[t] : (I.recalculateHSV(this), this.__state[t]) 49 | }, 50 | set: function (e) { 51 | "HSV" !== this.__state.space && (I.recalculateHSV(this), this.__state.space = "HSV"), this.__state[t] = e 52 | } 53 | }) 54 | } 55 | 56 | function i(e) { 57 | if ("0" === e || S.isUndefined(e)) return 0; 58 | var t = e.match(U); 59 | return S.isNull(t) ? 0 : parseFloat(t[1]) 60 | } 61 | 62 | function r(e) { 63 | var t = e.toString(); 64 | return t.indexOf(".") > -1 ? t.length - t.indexOf(".") - 1 : 0 65 | } 66 | 67 | function s(e, t) { 68 | var n = Math.pow(10, t); 69 | return Math.round(e * n) / n 70 | } 71 | 72 | function a(e, t, n, o, i) { 73 | return o + (e - t) / (n - t) * (i - o) 74 | } 75 | 76 | function l(e, t, n, o) { 77 | e.style.background = "", S.each(ee, function (i) { 78 | e.style.cssText += "background: " + i + "linear-gradient(" + t + ", " + n + " 0%, " + o + " 100%); " 79 | }) 80 | } 81 | 82 | function d(e) { 83 | e.style.background = "", e.style.cssText += "background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);", e.style.cssText += "background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);", e.style.cssText += "background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);", e.style.cssText += "background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);", e.style.cssText += "background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);" 84 | } 85 | 86 | function c(e, t, n) { 87 | var o = document.createElement("li"); 88 | return t && o.appendChild(t), n ? e.__ul.insertBefore(o, n) : e.__ul.appendChild(o), e.onResize(), o 89 | } 90 | 91 | function u(e) { 92 | X.unbind(window, "resize", e.__resizeHandler), e.saveToLocalStorageIfPossible && X.unbind(window, "unload", e.saveToLocalStorageIfPossible) 93 | } 94 | 95 | function _(e, t) { 96 | var n = e.__preset_select[e.__preset_select.selectedIndex]; 97 | n.innerHTML = t ? n.value + "*" : n.value 98 | } 99 | 100 | function h(e, t, n) { 101 | if (n.__li = t, n.__gui = e, S.extend(n, { 102 | options: function (t) { 103 | if (arguments.length > 1) { 104 | var o = n.__li.nextElementSibling; 105 | return n.remove(), f(e, n.object, n.property, { 106 | before: o, 107 | factoryArgs: [S.toArray(arguments)] 108 | }) 109 | } 110 | if (S.isArray(t) || S.isObject(t)) { 111 | var i = n.__li.nextElementSibling; 112 | return n.remove(), f(e, n.object, n.property, { 113 | before: i, 114 | factoryArgs: [t] 115 | }) 116 | } 117 | }, 118 | name: function (e) { 119 | return n.__li.firstElementChild.firstElementChild.innerHTML = e, n 120 | }, 121 | listen: function () { 122 | return n.__gui.listen(n), n 123 | }, 124 | remove: function () { 125 | return n.__gui.remove(n), n 126 | } 127 | }), n instanceof q) { 128 | var o = new Q(n.object, n.property, { 129 | min: n.__min, 130 | max: n.__max, 131 | step: n.__step 132 | }); 133 | S.each(["updateDisplay", "onChange", "onFinishChange", "step"], function (e) { 134 | var t = n[e], 135 | i = o[e]; 136 | n[e] = o[e] = function () { 137 | var e = Array.prototype.slice.call(arguments); 138 | return i.apply(o, e), t.apply(n, e) 139 | } 140 | }), X.addClass(t, "has-slider"), n.domElement.insertBefore(o.domElement, n.domElement.firstElementChild) 141 | } else if (n instanceof Q) { 142 | var i = function (t) { 143 | if (S.isNumber(n.__min) && S.isNumber(n.__max)) { 144 | var o = n.__li.firstElementChild.firstElementChild.innerHTML, 145 | i = n.__gui.__listening.indexOf(n) > -1; 146 | n.remove(); 147 | var r = f(e, n.object, n.property, { 148 | before: n.__li.nextElementSibling, 149 | factoryArgs: [n.__min, n.__max, n.__step] 150 | }); 151 | return r.name(o), i && r.listen(), r 152 | } 153 | return t 154 | }; 155 | n.min = S.compose(i, n.min), n.max = S.compose(i, n.max) 156 | } else n instanceof K ? (X.bind(t, "click", function () { 157 | X.fakeEvent(n.__checkbox, "click") 158 | }), X.bind(n.__checkbox, "click", function (e) { 159 | e.stopPropagation() 160 | })) : n instanceof Z ? (X.bind(t, "click", function () { 161 | X.fakeEvent(n.__button, "click") 162 | }), X.bind(t, "mouseover", function () { 163 | X.addClass(n.__button, "hover") 164 | }), X.bind(t, "mouseout", function () { 165 | X.removeClass(n.__button, "hover") 166 | })) : n instanceof $ && (X.addClass(t, "color"), n.updateDisplay = S.compose(function (e) { 167 | return t.style.borderLeftColor = n.__color.toString(), e 168 | }, n.updateDisplay), n.updateDisplay()); 169 | n.setValue = S.compose(function (t) { 170 | return e.getRoot().__preset_select && n.isModified() && _(e.getRoot(), !0), t 171 | }, n.setValue) 172 | } 173 | 174 | function p(e, t) { 175 | var n = e.getRoot(), 176 | o = n.__rememberedObjects.indexOf(t.object); 177 | if (-1 !== o) { 178 | var i = n.__rememberedObjectIndecesToControllers[o]; 179 | if (void 0 === i && (i = {}, n.__rememberedObjectIndecesToControllers[o] = i), i[t.property] = t, n.load && n.load.remembered) { 180 | var r = n.load.remembered, 181 | s = void 0; 182 | if (r[e.preset]) s = r[e.preset]; 183 | else { 184 | if (!r[se]) return; 185 | s = r[se] 186 | } 187 | if (s[o] && void 0 !== s[o][t.property]) { 188 | var a = s[o][t.property]; 189 | t.initialValue = a, t.setValue(a) 190 | } 191 | } 192 | } 193 | } 194 | 195 | function f(e, t, n, o) { 196 | if (void 0 === t[n]) throw new Error('Object "' + t + '" has no property "' + n + '"'); 197 | var i = void 0; 198 | if (o.color) i = new $(t, n); 199 | else { 200 | var r = [t, n].concat(o.factoryArgs); 201 | i = ne.apply(e, r) 202 | } 203 | o.before instanceof z && (o.before = o.before.__li), p(e, i), X.addClass(i.domElement, "c"); 204 | var s = document.createElement("span"); 205 | X.addClass(s, "property-name"), s.innerHTML = i.property; 206 | var a = document.createElement("div"); 207 | a.appendChild(s), a.appendChild(i.domElement); 208 | var l = c(e, a, o.before); 209 | return X.addClass(l, he.CLASS_CONTROLLER_ROW), i instanceof $ ? X.addClass(l, "color") : X.addClass(l, H(i.getValue())), h(e, l, i), e.__controllers.push(i), i 210 | } 211 | 212 | function m(e, t) { 213 | return document.location.href + "." + t 214 | } 215 | 216 | function g(e, t, n) { 217 | var o = document.createElement("option"); 218 | o.innerHTML = t, o.value = t, e.__preset_select.appendChild(o), n && (e.__preset_select.selectedIndex = e.__preset_select.length - 1) 219 | } 220 | 221 | function b(e, t) { 222 | t.style.display = e.useLocalStorage ? "block" : "none" 223 | } 224 | 225 | function v(e) { 226 | var t = e.__save_row = document.createElement("li"); 227 | X.addClass(e.domElement, "has-save"), e.__ul.insertBefore(t, e.__ul.firstChild), X.addClass(t, "save-row"); 228 | var n = document.createElement("span"); 229 | n.innerHTML = " ", X.addClass(n, "button gears"); 230 | var o = document.createElement("span"); 231 | o.innerHTML = "Save", X.addClass(o, "button"), X.addClass(o, "save"); 232 | var i = document.createElement("span"); 233 | i.innerHTML = "New", X.addClass(i, "button"), X.addClass(i, "save-as"); 234 | var r = document.createElement("span"); 235 | r.innerHTML = "Revert", X.addClass(r, "button"), X.addClass(r, "revert"); 236 | var s = e.__preset_select = document.createElement("select"); 237 | if (e.load && e.load.remembered ? S.each(e.load.remembered, function (t, n) { 238 | g(e, n, n === e.preset) 239 | }) : g(e, se, !1), X.bind(s, "change", function () { 240 | for (var t = 0; t < e.__preset_select.length; t++) e.__preset_select[t].innerHTML = e.__preset_select[t].value; 241 | e.preset = this.value 242 | }), t.appendChild(s), t.appendChild(n), t.appendChild(o), t.appendChild(i), t.appendChild(r), ae) { 243 | var a = document.getElementById("dg-local-explain"), 244 | l = document.getElementById("dg-local-storage"); 245 | document.getElementById("dg-save-locally").style.display = "block", "true" === localStorage.getItem(m(e, "isLocal")) && l.setAttribute("checked", "checked"), b(e, a), X.bind(l, "change", function () { 246 | e.useLocalStorage = !e.useLocalStorage, b(e, a) 247 | }) 248 | } 249 | var d = document.getElementById("dg-new-constructor"); 250 | X.bind(d, "keydown", function (e) { 251 | !e.metaKey || 67 !== e.which && 67 !== e.keyCode || le.hide() 252 | }), X.bind(n, "click", function () { 253 | d.innerHTML = JSON.stringify(e.getSaveObject(), void 0, 2), le.show(), d.focus(), d.select() 254 | }), X.bind(o, "click", function () { 255 | e.save() 256 | }), X.bind(i, "click", function () { 257 | var t = prompt("Enter a new preset name."); 258 | t && e.saveAs(t) 259 | }), X.bind(r, "click", function () { 260 | e.revert() 261 | }) 262 | } 263 | 264 | function y(e) { 265 | function t(t) { 266 | return t.preventDefault(), e.width += i - t.clientX, e.onResize(), i = t.clientX, !1 267 | } 268 | 269 | function n() { 270 | X.removeClass(e.__closeButton, he.CLASS_DRAG), X.unbind(window, "mousemove", t), X.unbind(window, "mouseup", n) 271 | } 272 | 273 | function o(o) { 274 | return o.preventDefault(), i = o.clientX, X.addClass(e.__closeButton, he.CLASS_DRAG), X.bind(window, "mousemove", t), X.bind(window, "mouseup", n), !1 275 | } 276 | var i = void 0; 277 | e.__resize_handle = document.createElement("div"), S.extend(e.__resize_handle.style, { 278 | width: "6px", 279 | marginLeft: "-3px", 280 | height: "200px", 281 | cursor: "ew-resize", 282 | position: "absolute" 283 | }), X.bind(e.__resize_handle, "mousedown", o), X.bind(e.__closeButton, "mousedown", o), e.domElement.insertBefore(e.__resize_handle, e.domElement.firstElementChild) 284 | } 285 | 286 | function w(e, t) { 287 | e.domElement.style.width = t + "px", e.__save_row && e.autoPlace && (e.__save_row.style.width = t + "px"), e.__closeButton && (e.__closeButton.style.width = t + "px") 288 | } 289 | 290 | function x(e, t) { 291 | var n = {}; 292 | return S.each(e.__rememberedObjects, function (o, i) { 293 | var r = {}, 294 | s = e.__rememberedObjectIndecesToControllers[i]; 295 | S.each(s, function (e, n) { 296 | r[n] = t ? e.initialValue : e.getValue() 297 | }), n[i] = r 298 | }), n 299 | } 300 | 301 | function E(e) { 302 | for (var t = 0; t < e.__preset_select.length; t++) e.__preset_select[t].value === e.preset && (e.__preset_select.selectedIndex = t) 303 | } 304 | 305 | function C(e) { 306 | 0 !== e.length && oe.call(window, function () { 307 | C(e) 308 | }), S.each(e, function (e) { 309 | e.updateDisplay() 310 | }) 311 | } 312 | var A = Array.prototype.forEach, 313 | k = Array.prototype.slice, 314 | S = { 315 | BREAK: {}, 316 | extend: function (e) { 317 | return this.each(k.call(arguments, 1), function (t) { 318 | (this.isObject(t) ? Object.keys(t) : []).forEach(function (n) { 319 | this.isUndefined(t[n]) || (e[n] = t[n]) 320 | }.bind(this)) 321 | }, this), e 322 | }, 323 | defaults: function (e) { 324 | return this.each(k.call(arguments, 1), function (t) { 325 | (this.isObject(t) ? Object.keys(t) : []).forEach(function (n) { 326 | this.isUndefined(e[n]) && (e[n] = t[n]) 327 | }.bind(this)) 328 | }, this), e 329 | }, 330 | compose: function () { 331 | var e = k.call(arguments); 332 | return function () { 333 | for (var t = k.call(arguments), n = e.length - 1; n >= 0; n--) t = [e[n].apply(this, t)]; 334 | return t[0] 335 | } 336 | }, 337 | each: function (e, t, n) { 338 | if (e) 339 | if (A && e.forEach && e.forEach === A) e.forEach(t, n); 340 | else if (e.length === e.length + 0) { 341 | var o = void 0, 342 | i = void 0; 343 | for (o = 0, i = e.length; o < i; o++) 344 | if (o in e && t.call(n, e[o], o) === this.BREAK) return 345 | } else 346 | for (var r in e) 347 | if (t.call(n, e[r], r) === this.BREAK) return 348 | }, 349 | defer: function (e) { 350 | setTimeout(e, 0) 351 | }, 352 | debounce: function (e, t, n) { 353 | var o = void 0; 354 | return function () { 355 | var i = this, 356 | r = arguments, 357 | s = n || !o; 358 | clearTimeout(o), o = setTimeout(function () { 359 | o = null, n || e.apply(i, r) 360 | }, t), s && e.apply(i, r) 361 | } 362 | }, 363 | toArray: function (e) { 364 | return e.toArray ? e.toArray() : k.call(e) 365 | }, 366 | isUndefined: function (e) { 367 | return void 0 === e 368 | }, 369 | isNull: function (e) { 370 | return null === e 371 | }, 372 | isNaN: function (e) { 373 | function t(t) { 374 | return e.apply(this, arguments) 375 | } 376 | return t.toString = function () { 377 | return e.toString() 378 | }, t 379 | }(function (e) { 380 | return isNaN(e) 381 | }), 382 | isArray: Array.isArray || function (e) { 383 | return e.constructor === Array 384 | }, 385 | isObject: function (e) { 386 | return e === Object(e) 387 | }, 388 | isNumber: function (e) { 389 | return e === e + 0 390 | }, 391 | isString: function (e) { 392 | return e === e + "" 393 | }, 394 | isBoolean: function (e) { 395 | return !1 === e || !0 === e 396 | }, 397 | isFunction: function (e) { 398 | return "[object Function]" === Object.prototype.toString.call(e) 399 | } 400 | }, 401 | O = [{ 402 | litmus: S.isString, 403 | conversions: { 404 | THREE_CHAR_HEX: { 405 | read: function (e) { 406 | var t = e.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); 407 | return null !== t && { 408 | space: "HEX", 409 | hex: parseInt("0x" + t[1].toString() + t[1].toString() + t[2].toString() + t[2].toString() + t[3].toString() + t[3].toString(), 0) 410 | } 411 | }, 412 | write: t 413 | }, 414 | SIX_CHAR_HEX: { 415 | read: function (e) { 416 | var t = e.match(/^#([A-F0-9]{6})$/i); 417 | return null !== t && { 418 | space: "HEX", 419 | hex: parseInt("0x" + t[1].toString(), 0) 420 | } 421 | }, 422 | write: t 423 | }, 424 | CSS_RGB: { 425 | read: function (e) { 426 | var t = e.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 427 | return null !== t && { 428 | space: "RGB", 429 | r: parseFloat(t[1]), 430 | g: parseFloat(t[2]), 431 | b: parseFloat(t[3]) 432 | } 433 | }, 434 | write: t 435 | }, 436 | CSS_RGBA: { 437 | read: function (e) { 438 | var t = e.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 439 | return null !== t && { 440 | space: "RGB", 441 | r: parseFloat(t[1]), 442 | g: parseFloat(t[2]), 443 | b: parseFloat(t[3]), 444 | a: parseFloat(t[4]) 445 | } 446 | }, 447 | write: t 448 | } 449 | } 450 | }, { 451 | litmus: S.isNumber, 452 | conversions: { 453 | HEX: { 454 | read: function (e) { 455 | return { 456 | space: "HEX", 457 | hex: e, 458 | conversionName: "HEX" 459 | } 460 | }, 461 | write: function (e) { 462 | return e.hex 463 | } 464 | } 465 | } 466 | }, { 467 | litmus: S.isArray, 468 | conversions: { 469 | RGB_ARRAY: { 470 | read: function (e) { 471 | return 3 === e.length && { 472 | space: "RGB", 473 | r: e[0], 474 | g: e[1], 475 | b: e[2] 476 | } 477 | }, 478 | write: function (e) { 479 | return [e.r, e.g, e.b] 480 | } 481 | }, 482 | RGBA_ARRAY: { 483 | read: function (e) { 484 | return 4 === e.length && { 485 | space: "RGB", 486 | r: e[0], 487 | g: e[1], 488 | b: e[2], 489 | a: e[3] 490 | } 491 | }, 492 | write: function (e) { 493 | return [e.r, e.g, e.b, e.a] 494 | } 495 | } 496 | } 497 | }, { 498 | litmus: S.isObject, 499 | conversions: { 500 | RGBA_OBJ: { 501 | read: function (e) { 502 | return !!(S.isNumber(e.r) && S.isNumber(e.g) && S.isNumber(e.b) && S.isNumber(e.a)) && { 503 | space: "RGB", 504 | r: e.r, 505 | g: e.g, 506 | b: e.b, 507 | a: e.a 508 | } 509 | }, 510 | write: function (e) { 511 | return { 512 | r: e.r, 513 | g: e.g, 514 | b: e.b, 515 | a: e.a 516 | } 517 | } 518 | }, 519 | RGB_OBJ: { 520 | read: function (e) { 521 | return !!(S.isNumber(e.r) && S.isNumber(e.g) && S.isNumber(e.b)) && { 522 | space: "RGB", 523 | r: e.r, 524 | g: e.g, 525 | b: e.b 526 | } 527 | }, 528 | write: function (e) { 529 | return { 530 | r: e.r, 531 | g: e.g, 532 | b: e.b 533 | } 534 | } 535 | }, 536 | HSVA_OBJ: { 537 | read: function (e) { 538 | return !!(S.isNumber(e.h) && S.isNumber(e.s) && S.isNumber(e.v) && S.isNumber(e.a)) && { 539 | space: "HSV", 540 | h: e.h, 541 | s: e.s, 542 | v: e.v, 543 | a: e.a 544 | } 545 | }, 546 | write: function (e) { 547 | return { 548 | h: e.h, 549 | s: e.s, 550 | v: e.v, 551 | a: e.a 552 | } 553 | } 554 | }, 555 | HSV_OBJ: { 556 | read: function (e) { 557 | return !!(S.isNumber(e.h) && S.isNumber(e.s) && S.isNumber(e.v)) && { 558 | space: "HSV", 559 | h: e.h, 560 | s: e.s, 561 | v: e.v 562 | } 563 | }, 564 | write: function (e) { 565 | return { 566 | h: e.h, 567 | s: e.s, 568 | v: e.v 569 | } 570 | } 571 | } 572 | } 573 | }], 574 | T = void 0, 575 | L = void 0, 576 | R = function () { 577 | L = !1; 578 | var e = arguments.length > 1 ? S.toArray(arguments) : arguments[0]; 579 | return S.each(O, function (t) { 580 | if (t.litmus(e)) return S.each(t.conversions, function (t, n) { 581 | if (T = t.read(e), !1 === L && !1 !== T) return L = T, T.conversionName = n, T.conversion = t, S.BREAK 582 | }), S.BREAK 583 | }), L 584 | }, 585 | B = void 0, 586 | N = { 587 | hsv_to_rgb: function (e, t, n) { 588 | var o = Math.floor(e / 60) % 6, 589 | i = e / 60 - Math.floor(e / 60), 590 | r = n * (1 - t), 591 | s = n * (1 - i * t), 592 | a = n * (1 - (1 - i) * t), 593 | l = [ 594 | [n, a, r], 595 | [s, n, r], 596 | [r, n, a], 597 | [r, s, n], 598 | [a, r, n], 599 | [n, r, s] 600 | ][o]; 601 | return { 602 | r: 255 * l[0], 603 | g: 255 * l[1], 604 | b: 255 * l[2] 605 | } 606 | }, 607 | rgb_to_hsv: function (e, t, n) { 608 | var o = Math.min(e, t, n), 609 | i = Math.max(e, t, n), 610 | r = i - o, 611 | s = void 0, 612 | a = void 0; 613 | return 0 === i ? { 614 | h: NaN, 615 | s: 0, 616 | v: 0 617 | } : (a = r / i, s = e === i ? (t - n) / r : t === i ? 2 + (n - e) / r : 4 + (e - t) / r, (s /= 6) < 0 && (s += 1), { 618 | h: 360 * s, 619 | s: a, 620 | v: i / 255 621 | }) 622 | }, 623 | rgb_to_hex: function (e, t, n) { 624 | var o = this.hex_with_component(0, 2, e); 625 | return o = this.hex_with_component(o, 1, t), o = this.hex_with_component(o, 0, n) 626 | }, 627 | component_from_hex: function (e, t) { 628 | return e >> 8 * t & 255 629 | }, 630 | hex_with_component: function (e, t, n) { 631 | return n << (B = 8 * t) | e & ~(255 << B) 632 | } 633 | }, 634 | H = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (e) { 635 | return typeof e 636 | } : function (e) { 637 | return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e 638 | }, 639 | F = function (e, t) { 640 | if (!(e instanceof t)) throw new TypeError("Cannot call a class as a function") 641 | }, 642 | P = function () { 643 | function e(e, t) { 644 | for (var n = 0; n < t.length; n++) { 645 | var o = t[n]; 646 | o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, o.key, o) 647 | } 648 | } 649 | return function (t, n, o) { 650 | return n && e(t.prototype, n), o && e(t, o), t 651 | } 652 | }(), 653 | j = function e(t, n, o) { 654 | null === t && (t = Function.prototype); 655 | var i = Object.getOwnPropertyDescriptor(t, n); 656 | if (void 0 === i) { 657 | var r = Object.getPrototypeOf(t); 658 | return null === r ? void 0 : e(r, n, o) 659 | } 660 | if ("value" in i) return i.value; 661 | var s = i.get; 662 | if (void 0 !== s) return s.call(o) 663 | }, 664 | D = function (e, t) { 665 | if ("function" != typeof t && null !== t) throw new TypeError("Super expression must either be null or a function, not " + typeof t); 666 | e.prototype = Object.create(t && t.prototype, { 667 | constructor: { 668 | value: e, 669 | enumerable: !1, 670 | writable: !0, 671 | configurable: !0 672 | } 673 | }), t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : e.__proto__ = t) 674 | }, 675 | V = function (e, t) { 676 | if (!e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 677 | return !t || "object" != typeof t && "function" != typeof t ? e : t 678 | }, 679 | I = function () { 680 | function e() { 681 | if (F(this, e), this.__state = R.apply(this, arguments), !1 === this.__state) throw new Error("Failed to interpret color arguments"); 682 | this.__state.a = this.__state.a || 1 683 | } 684 | return P(e, [{ 685 | key: "toString", 686 | value: function () { 687 | return t(this) 688 | } 689 | }, { 690 | key: "toHexString", 691 | value: function () { 692 | return t(this, !0) 693 | } 694 | }, { 695 | key: "toOriginal", 696 | value: function () { 697 | return this.__state.conversion.write(this) 698 | } 699 | }]), e 700 | }(); 701 | I.recalculateRGB = function (e, t, n) { 702 | if ("HEX" === e.__state.space) e.__state[t] = N.component_from_hex(e.__state.hex, n); 703 | else { 704 | if ("HSV" !== e.__state.space) throw new Error("Corrupted color state"); 705 | S.extend(e.__state, N.hsv_to_rgb(e.__state.h, e.__state.s, e.__state.v)) 706 | } 707 | }, I.recalculateHSV = function (e) { 708 | var t = N.rgb_to_hsv(e.r, e.g, e.b); 709 | S.extend(e.__state, { 710 | s: t.s, 711 | v: t.v 712 | }), S.isNaN(t.h) ? S.isUndefined(e.__state.h) && (e.__state.h = 0) : e.__state.h = t.h 713 | }, I.COMPONENTS = ["r", "g", "b", "h", "s", "v", "hex", "a"], n(I.prototype, "r", 2), n(I.prototype, "g", 1), n(I.prototype, "b", 0), o(I.prototype, "h"), o(I.prototype, "s"), o(I.prototype, "v"), Object.defineProperty(I.prototype, "a", { 714 | get: function () { 715 | return this.__state.a 716 | }, 717 | set: function (e) { 718 | this.__state.a = e 719 | } 720 | }), Object.defineProperty(I.prototype, "hex", { 721 | get: function () { 722 | return "HEX" !== !this.__state.space && (this.__state.hex = N.rgb_to_hex(this.r, this.g, this.b)), this.__state.hex 723 | }, 724 | set: function (e) { 725 | this.__state.space = "HEX", this.__state.hex = e 726 | } 727 | }); 728 | var z = function () { 729 | function e(t, n) { 730 | F(this, e), this.initialValue = t[n], this.domElement = document.createElement("div"), this.object = t, this.property = n, this.__onChange = void 0, this.__onFinishChange = void 0 731 | } 732 | return P(e, [{ 733 | key: "onChange", 734 | value: function (e) { 735 | return this.__onChange = e, this 736 | } 737 | }, { 738 | key: "onFinishChange", 739 | value: function (e) { 740 | return this.__onFinishChange = e, this 741 | } 742 | }, { 743 | key: "setValue", 744 | value: function (e) { 745 | return this.object[this.property] = e, this.__onChange && this.__onChange.call(this, e), this.updateDisplay(), this 746 | } 747 | }, { 748 | key: "getValue", 749 | value: function () { 750 | return this.object[this.property] 751 | } 752 | }, { 753 | key: "updateDisplay", 754 | value: function () { 755 | return this 756 | } 757 | }, { 758 | key: "isModified", 759 | value: function () { 760 | return this.initialValue !== this.getValue() 761 | } 762 | }]), e 763 | }(), 764 | M = { 765 | HTMLEvents: ["change"], 766 | MouseEvents: ["click", "mousemove", "mousedown", "mouseup", "mouseover"], 767 | KeyboardEvents: ["keydown"] 768 | }, 769 | G = {}; 770 | S.each(M, function (e, t) { 771 | S.each(e, function (e) { 772 | G[e] = t 773 | }) 774 | }); 775 | var U = /(\d+(\.\d+)?)px/, 776 | X = { 777 | makeSelectable: function (e, t) { 778 | void 0 !== e && void 0 !== e.style && (e.onselectstart = t ? function () { 779 | return !1 780 | } : function () {}, e.style.MozUserSelect = t ? "auto" : "none", e.style.KhtmlUserSelect = t ? "auto" : "none", e.unselectable = t ? "on" : "off") 781 | }, 782 | makeFullscreen: function (e, t, n) { 783 | var o = n, 784 | i = t; 785 | S.isUndefined(i) && (i = !0), S.isUndefined(o) && (o = !0), e.style.position = "absolute", i && (e.style.left = 0, e.style.right = 0), o && (e.style.top = 0, e.style.bottom = 0) 786 | }, 787 | fakeEvent: function (e, t, n, o) { 788 | var i = n || {}, 789 | r = G[t]; 790 | if (!r) throw new Error("Event type " + t + " not supported."); 791 | var s = document.createEvent(r); 792 | switch (r) { 793 | case "MouseEvents": 794 | var a = i.x || i.clientX || 0, 795 | l = i.y || i.clientY || 0; 796 | s.initMouseEvent(t, i.bubbles || !1, i.cancelable || !0, window, i.clickCount || 1, 0, 0, a, l, !1, !1, !1, !1, 0, null); 797 | break; 798 | case "KeyboardEvents": 799 | var d = s.initKeyboardEvent || s.initKeyEvent; 800 | S.defaults(i, { 801 | cancelable: !0, 802 | ctrlKey: !1, 803 | altKey: !1, 804 | shiftKey: !1, 805 | metaKey: !1, 806 | keyCode: void 0, 807 | charCode: void 0 808 | }), d(t, i.bubbles || !1, i.cancelable, window, i.ctrlKey, i.altKey, i.shiftKey, i.metaKey, i.keyCode, i.charCode); 809 | break; 810 | default: 811 | s.initEvent(t, i.bubbles || !1, i.cancelable || !0) 812 | } 813 | S.defaults(s, o), e.dispatchEvent(s) 814 | }, 815 | bind: function (e, t, n, o) { 816 | var i = o || !1; 817 | return e.addEventListener ? e.addEventListener(t, n, i) : e.attachEvent && e.attachEvent("on" + t, n), X 818 | }, 819 | unbind: function (e, t, n, o) { 820 | var i = o || !1; 821 | return e.removeEventListener ? e.removeEventListener(t, n, i) : e.detachEvent && e.detachEvent("on" + t, n), X 822 | }, 823 | addClass: function (e, t) { 824 | if (void 0 === e.className) e.className = t; 825 | else if (e.className !== t) { 826 | var n = e.className.split(/ +/); - 1 === n.indexOf(t) && (n.push(t), e.className = n.join(" ").replace(/^\s+/, "").replace(/\s+$/, "")) 827 | } 828 | return X 829 | }, 830 | removeClass: function (e, t) { 831 | if (t) 832 | if (e.className === t) e.removeAttribute("class"); 833 | else { 834 | var n = e.className.split(/ +/), 835 | o = n.indexOf(t); - 1 !== o && (n.splice(o, 1), e.className = n.join(" ")) 836 | } 837 | else e.className = void 0; 838 | return X 839 | }, 840 | hasClass: function (e, t) { 841 | return new RegExp("(?:^|\\s+)" + t + "(?:\\s+|$)").test(e.className) || !1 842 | }, 843 | getWidth: function (e) { 844 | var t = getComputedStyle(e); 845 | return i(t["border-left-width"]) + i(t["border-right-width"]) + i(t["padding-left"]) + i(t["padding-right"]) + i(t.width) 846 | }, 847 | getHeight: function (e) { 848 | var t = getComputedStyle(e); 849 | return i(t["border-top-width"]) + i(t["border-bottom-width"]) + i(t["padding-top"]) + i(t["padding-bottom"]) + i(t.height) 850 | }, 851 | getOffset: function (e) { 852 | var t = e, 853 | n = { 854 | left: 0, 855 | top: 0 856 | }; 857 | if (t.offsetParent) 858 | do { 859 | n.left += t.offsetLeft, n.top += t.offsetTop, t = t.offsetParent 860 | } while (t); 861 | return n 862 | }, 863 | isActive: function (e) { 864 | return e === document.activeElement && (e.type || e.href) 865 | } 866 | }, 867 | K = function (e) { 868 | function t(e, n) { 869 | F(this, t); 870 | var o = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)), 871 | i = o; 872 | return o.__prev = o.getValue(), o.__checkbox = document.createElement("input"), o.__checkbox.setAttribute("type", "checkbox"), X.bind(o.__checkbox, "change", function () { 873 | i.setValue(!i.__prev) 874 | }, !1), o.domElement.appendChild(o.__checkbox), o.updateDisplay(), o 875 | } 876 | return D(t, z), P(t, [{ 877 | key: "setValue", 878 | value: function (e) { 879 | var n = j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "setValue", this).call(this, e); 880 | return this.__onFinishChange && this.__onFinishChange.call(this, this.getValue()), this.__prev = this.getValue(), n 881 | } 882 | }, { 883 | key: "updateDisplay", 884 | value: function () { 885 | return !0 === this.getValue() ? (this.__checkbox.setAttribute("checked", "checked"), this.__checkbox.checked = !0, this.__prev = !0) : (this.__checkbox.checked = !1, this.__prev = !1), j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "updateDisplay", this).call(this) 886 | } 887 | }]), t 888 | }(), 889 | Y = function (e) { 890 | function t(e, n, o) { 891 | F(this, t); 892 | var i = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)), 893 | r = o, 894 | s = i; 895 | if (i.__select = document.createElement("select"), S.isArray(r)) { 896 | var a = {}; 897 | S.each(r, function (e) { 898 | a[e] = e 899 | }), r = a 900 | } 901 | return S.each(r, function (e, t) { 902 | var n = document.createElement("option"); 903 | n.innerHTML = t, n.setAttribute("value", e), s.__select.appendChild(n) 904 | }), i.updateDisplay(), X.bind(i.__select, "change", function () { 905 | var e = this.options[this.selectedIndex].value; 906 | s.setValue(e) 907 | }), i.domElement.appendChild(i.__select), i 908 | } 909 | return D(t, z), P(t, [{ 910 | key: "setValue", 911 | value: function (e) { 912 | var n = j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "setValue", this).call(this, e); 913 | return this.__onFinishChange && this.__onFinishChange.call(this, this.getValue()), n 914 | } 915 | }, { 916 | key: "updateDisplay", 917 | value: function () { 918 | return X.isActive(this.__select) ? this : (this.__select.value = this.getValue(), j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "updateDisplay", this).call(this)) 919 | } 920 | }]), t 921 | }(), 922 | J = function (e) { 923 | function t(e, n) { 924 | function o() { 925 | r.setValue(r.__input.value) 926 | } 927 | F(this, t); 928 | var i = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)), 929 | r = i; 930 | return i.__input = document.createElement("input"), i.__input.setAttribute("type", "text"), X.bind(i.__input, "keyup", o), X.bind(i.__input, "change", o), X.bind(i.__input, "blur", function () { 931 | r.__onFinishChange && r.__onFinishChange.call(r, r.getValue()) 932 | }), X.bind(i.__input, "keydown", function (e) { 933 | 13 === e.keyCode && this.blur() 934 | }), i.updateDisplay(), i.domElement.appendChild(i.__input), i 935 | } 936 | return D(t, z), P(t, [{ 937 | key: "updateDisplay", 938 | value: function () { 939 | return X.isActive(this.__input) || (this.__input.value = this.getValue()), j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "updateDisplay", this).call(this) 940 | } 941 | }]), t 942 | }(), 943 | W = function (e) { 944 | function t(e, n, o) { 945 | F(this, t); 946 | var i = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)), 947 | s = o || {}; 948 | return i.__min = s.min, i.__max = s.max, i.__step = s.step, S.isUndefined(i.__step) ? 0 === i.initialValue ? i.__impliedStep = 1 : i.__impliedStep = Math.pow(10, Math.floor(Math.log(Math.abs(i.initialValue)) / Math.LN10)) / 10 : i.__impliedStep = i.__step, i.__precision = r(i.__impliedStep), i 949 | } 950 | return D(t, z), P(t, [{ 951 | key: "setValue", 952 | value: function (e) { 953 | var n = e; 954 | return void 0 !== this.__min && n < this.__min ? n = this.__min : void 0 !== this.__max && n > this.__max && (n = this.__max), void 0 !== this.__step && n % this.__step != 0 && (n = Math.round(n / this.__step) * this.__step), j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "setValue", this).call(this, n) 955 | } 956 | }, { 957 | key: "min", 958 | value: function (e) { 959 | return this.__min = e, this 960 | } 961 | }, { 962 | key: "max", 963 | value: function (e) { 964 | return this.__max = e, this 965 | } 966 | }, { 967 | key: "step", 968 | value: function (e) { 969 | return this.__step = e, this.__impliedStep = e, this.__precision = r(e), this 970 | } 971 | }]), t 972 | }(), 973 | Q = function (e) { 974 | function t(e, n, o) { 975 | function i() { 976 | l.__onFinishChange && l.__onFinishChange.call(l, l.getValue()) 977 | } 978 | 979 | function r(e) { 980 | var t = d - e.clientY; 981 | l.setValue(l.getValue() + t * l.__impliedStep), d = e.clientY 982 | } 983 | 984 | function s() { 985 | X.unbind(window, "mousemove", r), X.unbind(window, "mouseup", s), i() 986 | } 987 | F(this, t); 988 | var a = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n, o)); 989 | a.__truncationSuspended = !1; 990 | var l = a, 991 | d = void 0; 992 | return a.__input = document.createElement("input"), a.__input.setAttribute("type", "text"), X.bind(a.__input, "change", function () { 993 | var e = parseFloat(l.__input.value); 994 | S.isNaN(e) || l.setValue(e) 995 | }), X.bind(a.__input, "blur", function () { 996 | i() 997 | }), X.bind(a.__input, "mousedown", function (e) { 998 | X.bind(window, "mousemove", r), X.bind(window, "mouseup", s), d = e.clientY 999 | }), X.bind(a.__input, "keydown", function (e) { 1000 | 13 === e.keyCode && (l.__truncationSuspended = !0, this.blur(), l.__truncationSuspended = !1, i()) 1001 | }), a.updateDisplay(), a.domElement.appendChild(a.__input), a 1002 | } 1003 | return D(t, W), P(t, [{ 1004 | key: "updateDisplay", 1005 | value: function () { 1006 | return this.__input.value = this.__truncationSuspended ? this.getValue() : s(this.getValue(), this.__precision), j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "updateDisplay", this).call(this) 1007 | } 1008 | }]), t 1009 | }(), 1010 | q = function (e) { 1011 | function t(e, n, o, i, r) { 1012 | function s(e) { 1013 | e.preventDefault(); 1014 | var t = _.__background.getBoundingClientRect(); 1015 | return _.setValue(a(e.clientX, t.left, t.right, _.__min, _.__max)), !1 1016 | } 1017 | 1018 | function l() { 1019 | X.unbind(window, "mousemove", s), X.unbind(window, "mouseup", l), _.__onFinishChange && _.__onFinishChange.call(_, _.getValue()) 1020 | } 1021 | 1022 | function d(e) { 1023 | var t = e.touches[0].clientX, 1024 | n = _.__background.getBoundingClientRect(); 1025 | _.setValue(a(t, n.left, n.right, _.__min, _.__max)) 1026 | } 1027 | 1028 | function c() { 1029 | X.unbind(window, "touchmove", d), X.unbind(window, "touchend", c), _.__onFinishChange && _.__onFinishChange.call(_, _.getValue()) 1030 | } 1031 | F(this, t); 1032 | var u = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n, { 1033 | min: o, 1034 | max: i, 1035 | step: r 1036 | })), 1037 | _ = u; 1038 | return u.__background = document.createElement("div"), u.__foreground = document.createElement("div"), X.bind(u.__background, "mousedown", function (e) { 1039 | document.activeElement.blur(), X.bind(window, "mousemove", s), X.bind(window, "mouseup", l), s(e) 1040 | }), X.bind(u.__background, "touchstart", function (e) { 1041 | 1 === e.touches.length && (X.bind(window, "touchmove", d), X.bind(window, "touchend", c), d(e)) 1042 | }), X.addClass(u.__background, "slider"), X.addClass(u.__foreground, "slider-fg"), u.updateDisplay(), u.__background.appendChild(u.__foreground), u.domElement.appendChild(u.__background), u 1043 | } 1044 | return D(t, W), P(t, [{ 1045 | key: "updateDisplay", 1046 | value: function () { 1047 | var e = (this.getValue() - this.__min) / (this.__max - this.__min); 1048 | return this.__foreground.style.width = 100 * e + "%", j(t.prototype.__proto__ || Object.getPrototypeOf(t.prototype), "updateDisplay", this).call(this) 1049 | } 1050 | }]), t 1051 | }(), 1052 | Z = function (e) { 1053 | function t(e, n, o) { 1054 | F(this, t); 1055 | var i = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)), 1056 | r = i; 1057 | return i.__button = document.createElement("div"), i.__button.innerHTML = void 0 === o ? "Fire" : o, X.bind(i.__button, "click", function (e) { 1058 | return e.preventDefault(), r.fire(), !1 1059 | }), X.addClass(i.__button, "button"), i.domElement.appendChild(i.__button), i 1060 | } 1061 | return D(t, z), P(t, [{ 1062 | key: "fire", 1063 | value: function () { 1064 | this.__onChange && this.__onChange.call(this), this.getValue().call(this.object), this.__onFinishChange && this.__onFinishChange.call(this, this.getValue()) 1065 | } 1066 | }]), t 1067 | }(), 1068 | $ = function (e) { 1069 | function t(e, n) { 1070 | function o(e) { 1071 | u(e), X.bind(window, "mousemove", u), X.bind(window, "touchmove", u), X.bind(window, "mouseup", r), X.bind(window, "touchend", r) 1072 | } 1073 | 1074 | function i(e) { 1075 | _(e), X.bind(window, "mousemove", _), X.bind(window, "touchmove", _), X.bind(window, "mouseup", s), X.bind(window, "touchend", s) 1076 | } 1077 | 1078 | function r() { 1079 | X.unbind(window, "mousemove", u), X.unbind(window, "touchmove", u), X.unbind(window, "mouseup", r), X.unbind(window, "touchend", r), c() 1080 | } 1081 | 1082 | function s() { 1083 | X.unbind(window, "mousemove", _), X.unbind(window, "touchmove", _), X.unbind(window, "mouseup", s), X.unbind(window, "touchend", s), c() 1084 | } 1085 | 1086 | function a() { 1087 | var e = R(this.value); 1088 | !1 !== e ? (p.__color.__state = e, p.setValue(p.__color.toOriginal())) : this.value = p.__color.toString() 1089 | } 1090 | 1091 | function c() { 1092 | p.__onFinishChange && p.__onFinishChange.call(p, p.__color.toOriginal()) 1093 | } 1094 | 1095 | function u(e) { 1096 | -1 === e.type.indexOf("touch") && e.preventDefault(); 1097 | var t = p.__saturation_field.getBoundingClientRect(), 1098 | n = e.touches && e.touches[0] || e, 1099 | o = n.clientX, 1100 | i = n.clientY, 1101 | r = (o - t.left) / (t.right - t.left), 1102 | s = 1 - (i - t.top) / (t.bottom - t.top); 1103 | return s > 1 ? s = 1 : s < 0 && (s = 0), r > 1 ? r = 1 : r < 0 && (r = 0), p.__color.v = s, p.__color.s = r, p.setValue(p.__color.toOriginal()), !1 1104 | } 1105 | 1106 | function _(e) { 1107 | -1 === e.type.indexOf("touch") && e.preventDefault(); 1108 | var t = p.__hue_field.getBoundingClientRect(), 1109 | n = 1 - ((e.touches && e.touches[0] || e).clientY - t.top) / (t.bottom - t.top); 1110 | return n > 1 ? n = 1 : n < 0 && (n = 0), p.__color.h = 360 * n, p.setValue(p.__color.toOriginal()), !1 1111 | } 1112 | F(this, t); 1113 | var h = V(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this, e, n)); 1114 | h.__color = new I(h.getValue()), h.__temp = new I(0); 1115 | var p = h; 1116 | h.domElement = document.createElement("div"), X.makeSelectable(h.domElement, !1), h.__selector = document.createElement("div"), h.__selector.className = "selector", h.__saturation_field = document.createElement("div"), h.__saturation_field.className = "saturation-field", h.__field_knob = document.createElement("div"), h.__field_knob.className = "field-knob", h.__field_knob_border = "2px solid ", h.__hue_knob = document.createElement("div"), h.__hue_knob.className = "hue-knob", h.__hue_field = document.createElement("div"), h.__hue_field.className = "hue-field", h.__input = document.createElement("input"), h.__input.type = "text", h.__input_textShadow = "0 1px 1px ", X.bind(h.__input, "keydown", function (e) { 1117 | 13 === e.keyCode && a.call(this) 1118 | }), X.bind(h.__input, "blur", a), X.bind(h.__selector, "mousedown", function () { 1119 | X.addClass(this, "drag").bind(window, "mouseup", function () { 1120 | X.removeClass(p.__selector, "drag") 1121 | }) 1122 | }), X.bind(h.__selector, "touchstart", function () { 1123 | X.addClass(this, "drag").bind(window, "touchend", function () { 1124 | X.removeClass(p.__selector, "drag") 1125 | }) 1126 | }); 1127 | var f = document.createElement("div"); 1128 | return S.extend(h.__selector.style, { 1129 | width: "122px", 1130 | height: "102px", 1131 | padding: "3px", 1132 | backgroundColor: "#222", 1133 | boxShadow: "0px 1px 3px rgba(0,0,0,0.3)" 1134 | }), S.extend(h.__field_knob.style, { 1135 | position: "absolute", 1136 | width: "12px", 1137 | height: "12px", 1138 | border: h.__field_knob_border + (h.__color.v < .5 ? "#fff" : "#000"), 1139 | boxShadow: "0px 1px 3px rgba(0,0,0,0.5)", 1140 | borderRadius: "12px", 1141 | zIndex: 1 1142 | }), S.extend(h.__hue_knob.style, { 1143 | position: "absolute", 1144 | width: "15px", 1145 | height: "2px", 1146 | borderRight: "4px solid #fff", 1147 | zIndex: 1 1148 | }), S.extend(h.__saturation_field.style, { 1149 | width: "100px", 1150 | height: "100px", 1151 | border: "1px solid #555", 1152 | marginRight: "3px", 1153 | display: "inline-block", 1154 | cursor: "pointer" 1155 | }), S.extend(f.style, { 1156 | width: "100%", 1157 | height: "100%", 1158 | background: "none" 1159 | }), l(f, "top", "rgba(0,0,0,0)", "#000"), S.extend(h.__hue_field.style, { 1160 | width: "15px", 1161 | height: "100px", 1162 | border: "1px solid #555", 1163 | cursor: "ns-resize", 1164 | position: "absolute", 1165 | top: "3px", 1166 | right: "3px" 1167 | }), d(h.__hue_field), S.extend(h.__input.style, { 1168 | outline: "none", 1169 | textAlign: "center", 1170 | color: "#fff", 1171 | border: 0, 1172 | fontWeight: "bold", 1173 | textShadow: h.__input_textShadow + "rgba(0,0,0,0.7)" 1174 | }), X.bind(h.__saturation_field, "mousedown", o), X.bind(h.__saturation_field, "touchstart", o), X.bind(h.__field_knob, "mousedown", o), X.bind(h.__field_knob, "touchstart", o), X.bind(h.__hue_field, "mousedown", i), X.bind(h.__hue_field, "touchstart", i), h.__saturation_field.appendChild(f), h.__selector.appendChild(h.__field_knob), h.__selector.appendChild(h.__saturation_field), h.__selector.appendChild(h.__hue_field), h.__hue_field.appendChild(h.__hue_knob), h.domElement.appendChild(h.__input), h.domElement.appendChild(h.__selector), h.updateDisplay(), h 1175 | } 1176 | return D(t, z), P(t, [{ 1177 | key: "updateDisplay", 1178 | value: function () { 1179 | var e = R(this.getValue()); 1180 | if (!1 !== e) { 1181 | var t = !1; 1182 | S.each(I.COMPONENTS, function (n) { 1183 | if (!S.isUndefined(e[n]) && !S.isUndefined(this.__color.__state[n]) && e[n] !== this.__color.__state[n]) return t = !0, {} 1184 | }, this), t && S.extend(this.__color.__state, e) 1185 | } 1186 | S.extend(this.__temp.__state, this.__color.__state), this.__temp.a = 1; 1187 | var n = this.__color.v < .5 || this.__color.s > .5 ? 255 : 0, 1188 | o = 255 - n; 1189 | S.extend(this.__field_knob.style, { 1190 | marginLeft: 100 * this.__color.s - 7 + "px", 1191 | marginTop: 100 * (1 - this.__color.v) - 7 + "px", 1192 | backgroundColor: this.__temp.toHexString(), 1193 | border: this.__field_knob_border + "rgb(" + n + "," + n + "," + n + ")" 1194 | }), this.__hue_knob.style.marginTop = 100 * (1 - this.__color.h / 360) + "px", this.__temp.s = 1, this.__temp.v = 1, l(this.__saturation_field, "left", "#fff", this.__temp.toHexString()), this.__input.value = this.__color.toString(), S.extend(this.__input.style, { 1195 | backgroundColor: this.__color.toHexString(), 1196 | color: "rgb(" + n + "," + n + "," + n + ")", 1197 | textShadow: this.__input_textShadow + "rgba(" + o + "," + o + "," + o + ",.7)" 1198 | }) 1199 | } 1200 | }]), t 1201 | }(), 1202 | ee = ["-moz-", "-o-", "-webkit-", "-ms-", ""], 1203 | te = { 1204 | load: function (e, t) { 1205 | var n = t || document, 1206 | o = n.createElement("link"); 1207 | o.type = "text/css", o.rel = "stylesheet", o.href = e, n.getElementsByTagName("head")[0].appendChild(o) 1208 | }, 1209 | inject: function (e, t) { 1210 | var n = t || document, 1211 | o = document.createElement("style"); 1212 | o.type = "text/css", o.innerHTML = e; 1213 | var i = n.getElementsByTagName("head")[0]; 1214 | try { 1215 | i.appendChild(o) 1216 | } catch (e) {} 1217 | } 1218 | }, 1219 | ne = function (e, t) { 1220 | var n = e[t]; 1221 | return S.isArray(arguments[2]) || S.isObject(arguments[2]) ? new Y(e, t, arguments[2]) : S.isNumber(n) ? S.isNumber(arguments[2]) && S.isNumber(arguments[3]) ? S.isNumber(arguments[4]) ? new q(e, t, arguments[2], arguments[3], arguments[4]) : new q(e, t, arguments[2], arguments[3]) : S.isNumber(arguments[4]) ? new Q(e, t, { 1222 | min: arguments[2], 1223 | max: arguments[3], 1224 | step: arguments[4] 1225 | }) : new Q(e, t, { 1226 | min: arguments[2], 1227 | max: arguments[3] 1228 | }) : S.isString(n) ? new J(e, t) : S.isFunction(n) ? new Z(e, t, "") : S.isBoolean(n) ? new K(e, t) : null 1229 | }, 1230 | oe = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (e) { 1231 | setTimeout(e, 1e3 / 60) 1232 | }, 1233 | ie = function () { 1234 | function e() { 1235 | F(this, e), this.backgroundElement = document.createElement("div"), S.extend(this.backgroundElement.style, { 1236 | backgroundColor: "rgba(0,0,0,0.8)", 1237 | top: 0, 1238 | left: 0, 1239 | display: "none", 1240 | zIndex: "1000", 1241 | opacity: 0, 1242 | WebkitTransition: "opacity 0.2s linear", 1243 | transition: "opacity 0.2s linear" 1244 | }), X.makeFullscreen(this.backgroundElement), this.backgroundElement.style.position = "fixed", this.domElement = document.createElement("div"), S.extend(this.domElement.style, { 1245 | position: "fixed", 1246 | display: "none", 1247 | zIndex: "1001", 1248 | opacity: 0, 1249 | WebkitTransition: "-webkit-transform 0.2s ease-out, opacity 0.2s linear", 1250 | transition: "transform 0.2s ease-out, opacity 0.2s linear" 1251 | }), document.body.appendChild(this.backgroundElement), document.body.appendChild(this.domElement); 1252 | var t = this; 1253 | X.bind(this.backgroundElement, "click", function () { 1254 | t.hide() 1255 | }) 1256 | } 1257 | return P(e, [{ 1258 | key: "show", 1259 | value: function () { 1260 | var e = this; 1261 | this.backgroundElement.style.display = "block", this.domElement.style.display = "block", this.domElement.style.opacity = 0, this.domElement.style.webkitTransform = "scale(1.1)", this.layout(), S.defer(function () { 1262 | e.backgroundElement.style.opacity = 1, e.domElement.style.opacity = 1, e.domElement.style.webkitTransform = "scale(1)" 1263 | }) 1264 | } 1265 | }, { 1266 | key: "hide", 1267 | value: function () { 1268 | var e = this, 1269 | t = function t() { 1270 | e.domElement.style.display = "none", e.backgroundElement.style.display = "none", X.unbind(e.domElement, "webkitTransitionEnd", t), X.unbind(e.domElement, "transitionend", t), X.unbind(e.domElement, "oTransitionEnd", t) 1271 | }; 1272 | X.bind(this.domElement, "webkitTransitionEnd", t), X.bind(this.domElement, "transitionend", t), X.bind(this.domElement, "oTransitionEnd", t), this.backgroundElement.style.opacity = 0, this.domElement.style.opacity = 0, this.domElement.style.webkitTransform = "scale(1.1)" 1273 | } 1274 | }, { 1275 | key: "layout", 1276 | value: function () { 1277 | this.domElement.style.left = window.innerWidth / 2 - X.getWidth(this.domElement) / 2 + "px", this.domElement.style.top = window.innerHeight / 2 - X.getHeight(this.domElement) / 2 + "px" 1278 | } 1279 | }]), e 1280 | }(), 1281 | re = function (e) { 1282 | if (e && "undefined" != typeof window) { 1283 | var t = document.createElement("style"); 1284 | return t.setAttribute("type", "text/css"), t.innerHTML = e, document.head.appendChild(t), e 1285 | } 1286 | }(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n"); 1287 | te.inject(re); 1288 | var se = "Default", 1289 | ae = function () { 1290 | try { 1291 | return !!window.localStorage 1292 | } catch (e) { 1293 | return !1 1294 | } 1295 | }(), 1296 | le = void 0, 1297 | de = !0, 1298 | ce = void 0, 1299 | ue = !1, 1300 | _e = [], 1301 | he = function e(t) { 1302 | var n = this, 1303 | o = t || {}; 1304 | this.domElement = document.createElement("div"), this.__ul = document.createElement("ul"), this.domElement.appendChild(this.__ul), X.addClass(this.domElement, "dg"), this.__folders = {}, this.__controllers = [], this.__rememberedObjects = [], this.__rememberedObjectIndecesToControllers = [], this.__listening = [], o = S.defaults(o, { 1305 | closeOnTop: !1, 1306 | autoPlace: !0, 1307 | width: e.DEFAULT_WIDTH 1308 | }), o = S.defaults(o, { 1309 | resizable: o.autoPlace, 1310 | hideable: o.autoPlace 1311 | }), S.isUndefined(o.load) ? o.load = { 1312 | preset: se 1313 | } : o.preset && (o.load.preset = o.preset), S.isUndefined(o.parent) && o.hideable && _e.push(this), o.resizable = S.isUndefined(o.parent) && o.resizable, o.autoPlace && S.isUndefined(o.scrollable) && (o.scrollable = !0); 1314 | var i = ae && "true" === localStorage.getItem(m(this, "isLocal")), 1315 | r = void 0, 1316 | s = void 0; 1317 | if (Object.defineProperties(this, { 1318 | parent: { 1319 | get: function () { 1320 | return o.parent 1321 | } 1322 | }, 1323 | scrollable: { 1324 | get: function () { 1325 | return o.scrollable 1326 | } 1327 | }, 1328 | autoPlace: { 1329 | get: function () { 1330 | return o.autoPlace 1331 | } 1332 | }, 1333 | closeOnTop: { 1334 | get: function () { 1335 | return o.closeOnTop 1336 | } 1337 | }, 1338 | preset: { 1339 | get: function () { 1340 | return n.parent ? n.getRoot().preset : o.load.preset 1341 | }, 1342 | set: function (e) { 1343 | n.parent ? n.getRoot().preset = e : o.load.preset = e, E(this), n.revert() 1344 | } 1345 | }, 1346 | width: { 1347 | get: function () { 1348 | return o.width 1349 | }, 1350 | set: function (e) { 1351 | o.width = e, w(n, e) 1352 | } 1353 | }, 1354 | name: { 1355 | get: function () { 1356 | return o.name 1357 | }, 1358 | set: function (e) { 1359 | o.name = e, s && (s.innerHTML = o.name) 1360 | } 1361 | }, 1362 | closed: { 1363 | get: function () { 1364 | return o.closed 1365 | }, 1366 | set: function (t) { 1367 | o.closed = t, o.closed ? X.addClass(n.__ul, e.CLASS_CLOSED) : X.removeClass(n.__ul, e.CLASS_CLOSED), this.onResize(), n.__closeButton && (n.__closeButton.innerHTML = t ? e.TEXT_OPEN : e.TEXT_CLOSED) 1368 | } 1369 | }, 1370 | load: { 1371 | get: function () { 1372 | return o.load 1373 | } 1374 | }, 1375 | useLocalStorage: { 1376 | get: function () { 1377 | return i 1378 | }, 1379 | set: function (e) { 1380 | ae && (i = e, e ? X.bind(window, "unload", r) : X.unbind(window, "unload", r), localStorage.setItem(m(n, "isLocal"), e)) 1381 | } 1382 | } 1383 | }), S.isUndefined(o.parent)) { 1384 | if (o.closed = !1, X.addClass(this.domElement, e.CLASS_MAIN), X.makeSelectable(this.domElement, !1), ae && i) { 1385 | n.useLocalStorage = !0; 1386 | var a = localStorage.getItem(m(this, "gui")); 1387 | a && (o.load = JSON.parse(a)) 1388 | } 1389 | this.__closeButton = document.createElement("div"), this.__closeButton.innerHTML = e.TEXT_CLOSED, X.addClass(this.__closeButton, e.CLASS_CLOSE_BUTTON), o.closeOnTop ? (X.addClass(this.__closeButton, e.CLASS_CLOSE_TOP), this.domElement.insertBefore(this.__closeButton, this.domElement.childNodes[0])) : (X.addClass(this.__closeButton, e.CLASS_CLOSE_BOTTOM), this.domElement.appendChild(this.__closeButton)), X.bind(this.__closeButton, "click", function () { 1390 | n.closed = !n.closed 1391 | }) 1392 | } else { 1393 | void 0 === o.closed && (o.closed = !0); 1394 | var l = document.createTextNode(o.name); 1395 | X.addClass(l, "controller-name"), s = c(n, l); 1396 | X.addClass(this.__ul, e.CLASS_CLOSED), X.addClass(s, "title"), X.bind(s, "click", function (e) { 1397 | return e.preventDefault(), n.closed = !n.closed, !1 1398 | }), o.closed || (this.closed = !1) 1399 | } 1400 | o.autoPlace && (S.isUndefined(o.parent) && (de && (ce = document.createElement("div"), X.addClass(ce, "dg"), X.addClass(ce, e.CLASS_AUTO_PLACE_CONTAINER), document.body.appendChild(ce), de = !1), ce.appendChild(this.domElement), X.addClass(this.domElement, e.CLASS_AUTO_PLACE)), this.parent || w(n, o.width)), this.__resizeHandler = function () { 1401 | n.onResizeDebounced() 1402 | }, X.bind(window, "resize", this.__resizeHandler), X.bind(this.__ul, "webkitTransitionEnd", this.__resizeHandler), X.bind(this.__ul, "transitionend", this.__resizeHandler), X.bind(this.__ul, "oTransitionEnd", this.__resizeHandler), this.onResize(), o.resizable && y(this), r = function () { 1403 | ae && "true" === localStorage.getItem(m(n, "isLocal")) && localStorage.setItem(m(n, "gui"), JSON.stringify(n.getSaveObject())) 1404 | }, this.saveToLocalStorageIfPossible = r, o.parent || function () { 1405 | var e = n.getRoot(); 1406 | e.width += 1, S.defer(function () { 1407 | e.width -= 1 1408 | }) 1409 | }() 1410 | }; 1411 | he.toggleHide = function () { 1412 | ue = !ue, S.each(_e, function (e) { 1413 | e.domElement.style.display = ue ? "none" : "" 1414 | }) 1415 | }, he.CLASS_AUTO_PLACE = "a", he.CLASS_AUTO_PLACE_CONTAINER = "ac", he.CLASS_MAIN = "main", he.CLASS_CONTROLLER_ROW = "cr", he.CLASS_TOO_TALL = "taller-than-window", he.CLASS_CLOSED = "closed", he.CLASS_CLOSE_BUTTON = "close-button", he.CLASS_CLOSE_TOP = "close-top", he.CLASS_CLOSE_BOTTOM = "close-bottom", he.CLASS_DRAG = "drag", he.DEFAULT_WIDTH = 245, he.TEXT_CLOSED = "Close Controls", he.TEXT_OPEN = "Open Controls", he._keydownHandler = function (e) { 1416 | "text" === document.activeElement.type || 72 !== e.which && 72 !== e.keyCode || he.toggleHide() 1417 | }, X.bind(window, "keydown", he._keydownHandler, !1), S.extend(he.prototype, { 1418 | add: function (e, t) { 1419 | return f(this, e, t, { 1420 | factoryArgs: Array.prototype.slice.call(arguments, 2) 1421 | }) 1422 | }, 1423 | addColor: function (e, t) { 1424 | return f(this, e, t, { 1425 | color: !0 1426 | }) 1427 | }, 1428 | remove: function (e) { 1429 | this.__ul.removeChild(e.__li), this.__controllers.splice(this.__controllers.indexOf(e), 1); 1430 | var t = this; 1431 | S.defer(function () { 1432 | t.onResize() 1433 | }) 1434 | }, 1435 | destroy: function () { 1436 | if (this.parent) throw new Error("Only the root GUI should be removed with .destroy(). For subfolders, use gui.removeFolder(folder) instead."); 1437 | this.autoPlace && ce.removeChild(this.domElement); 1438 | var e = this; 1439 | S.each(this.__folders, function (t) { 1440 | e.removeFolder(t) 1441 | }), X.unbind(window, "keydown", he._keydownHandler, !1), u(this) 1442 | }, 1443 | addFolder: function (e) { 1444 | if (void 0 !== this.__folders[e]) throw new Error('You already have a folder in this GUI by the name "' + e + '"'); 1445 | var t = { 1446 | name: e, 1447 | parent: this 1448 | }; 1449 | t.autoPlace = this.autoPlace, this.load && this.load.folders && this.load.folders[e] && (t.closed = this.load.folders[e].closed, t.load = this.load.folders[e]); 1450 | var n = new he(t); 1451 | this.__folders[e] = n; 1452 | var o = c(this, n.domElement); 1453 | return X.addClass(o, "folder"), n 1454 | }, 1455 | removeFolder: function (e) { 1456 | this.__ul.removeChild(e.domElement.parentElement), delete this.__folders[e.name], this.load && this.load.folders && this.load.folders[e.name] && delete this.load.folders[e.name], u(e); 1457 | var t = this; 1458 | S.each(e.__folders, function (t) { 1459 | e.removeFolder(t) 1460 | }), S.defer(function () { 1461 | t.onResize() 1462 | }) 1463 | }, 1464 | open: function () { 1465 | this.closed = !1 1466 | }, 1467 | close: function () { 1468 | this.closed = !0 1469 | }, 1470 | onResize: function () { 1471 | var e = this.getRoot(); 1472 | if (e.scrollable) { 1473 | var t = X.getOffset(e.__ul).top, 1474 | n = 0; 1475 | S.each(e.__ul.childNodes, function (t) { 1476 | e.autoPlace && t === e.__save_row || (n += X.getHeight(t)) 1477 | }), window.innerHeight - t - 20 < n ? (X.addClass(e.domElement, he.CLASS_TOO_TALL), e.__ul.style.height = window.innerHeight - t - 20 + "px") : (X.removeClass(e.domElement, he.CLASS_TOO_TALL), e.__ul.style.height = "auto") 1478 | } 1479 | e.__resize_handle && S.defer(function () { 1480 | e.__resize_handle.style.height = e.__ul.offsetHeight + "px" 1481 | }), e.__closeButton && (e.__closeButton.style.width = e.width + "px") 1482 | }, 1483 | onResizeDebounced: S.debounce(function () { 1484 | this.onResize() 1485 | }, 50), 1486 | remember: function () { 1487 | if (S.isUndefined(le) && ((le = new ie).domElement.innerHTML = '
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n\n
\n\n
\n\n
'), this.parent) throw new Error("You can only call remember on a top level GUI."); 1488 | var e = this; 1489 | S.each(Array.prototype.slice.call(arguments), function (t) { 1490 | 0 === e.__rememberedObjects.length && v(e), -1 === e.__rememberedObjects.indexOf(t) && e.__rememberedObjects.push(t) 1491 | }), this.autoPlace && w(this, this.width) 1492 | }, 1493 | getRoot: function () { 1494 | for (var e = this; e.parent;) e = e.parent; 1495 | return e 1496 | }, 1497 | getSaveObject: function () { 1498 | var e = this.load; 1499 | return e.closed = this.closed, this.__rememberedObjects.length > 0 && (e.preset = this.preset, e.remembered || (e.remembered = {}), e.remembered[this.preset] = x(this)), e.folders = {}, S.each(this.__folders, function (t, n) { 1500 | e.folders[n] = t.getSaveObject() 1501 | }), e 1502 | }, 1503 | save: function () { 1504 | this.load.remembered || (this.load.remembered = {}), this.load.remembered[this.preset] = x(this), _(this, !1), this.saveToLocalStorageIfPossible() 1505 | }, 1506 | saveAs: function (e) { 1507 | this.load.remembered || (this.load.remembered = {}, this.load.remembered[se] = x(this, !0)), this.load.remembered[e] = x(this), this.preset = e, g(this, e, !0), this.saveToLocalStorageIfPossible() 1508 | }, 1509 | revert: function (e) { 1510 | S.each(this.__controllers, function (t) { 1511 | this.getRoot().load.remembered ? p(e || this.getRoot(), t) : t.setValue(t.initialValue), t.__onFinishChange && t.__onFinishChange.call(t, t.getValue()) 1512 | }, this), S.each(this.__folders, function (e) { 1513 | e.revert(e) 1514 | }), e || _(this.getRoot(), !1) 1515 | }, 1516 | listen: function (e) { 1517 | var t = 0 === this.__listening.length; 1518 | this.__listening.push(e), t && C(this.__listening) 1519 | }, 1520 | updateDisplay: function () { 1521 | S.each(this.__controllers, function (e) { 1522 | e.updateDisplay() 1523 | }), S.each(this.__folders, function (e) { 1524 | e.updateDisplay() 1525 | }) 1526 | } 1527 | }); 1528 | var pe = { 1529 | Color: I, 1530 | math: N, 1531 | interpret: R 1532 | }, 1533 | fe = { 1534 | Controller: z, 1535 | BooleanController: K, 1536 | OptionController: Y, 1537 | StringController: J, 1538 | NumberController: W, 1539 | NumberControllerBox: Q, 1540 | NumberControllerSlider: q, 1541 | FunctionController: Z, 1542 | ColorController: $ 1543 | }, 1544 | me = { 1545 | dom: X 1546 | }, 1547 | ge = { 1548 | GUI: he 1549 | }, 1550 | be = he, 1551 | ve = { 1552 | color: pe, 1553 | controllers: fe, 1554 | dom: me, 1555 | gui: ge, 1556 | GUI: be 1557 | }; 1558 | e.color = pe, e.controllers = fe, e.dom = me, e.gui = ge, e.GUI = be, e.default = ve, Object.defineProperty(e, "__esModule", { 1559 | value: !0 1560 | }) 1561 | }); -------------------------------------------------------------------------------- /etc/gcx/default.gcx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kopiro/siriwave/744cb00235b7900733efff25dc73b4d1ab77c92d/etc/gcx/default.gcx -------------------------------------------------------------------------------- /etc/gcx/ios9.gcx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kopiro/siriwave/744cb00235b7900733efff25dc73b4d1ab77c92d/etc/gcx/ios9.gcx -------------------------------------------------------------------------------- /etc/ios9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kopiro/siriwave/744cb00235b7900733efff25dc73b4d1ab77c92d/etc/ios9.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Siriwave 7 | 41 | 42 | 43 | 44 | 45 |

SiriwaveJS

46 |

The Apple® Siri wave-form
replicated in a JS library.

47 | 48 |
49 | 50 | 51 | 52 | 56 | 60 | 61 |
53 |

Classic

54 |
55 |
57 |

iOS9+

58 |
59 |
62 | 63 |
64 | Installation guide and source code available 65 | here. 66 |
67 | 68 | 90 | 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "siriwave", 3 | "version": "2.4.0", 4 | "description": "The Siri wave replicated in a JS library.", 5 | "module": "./dist/siriwave.esm.js", 6 | "main": "./dist/siriwave.esm.js", 7 | "browser": "./dist/siriwave.umd.js", 8 | "types": "./dist/types", 9 | "umdName": "SiriWave", 10 | "files": [ 11 | "/dist", 12 | "/src" 13 | ], 14 | "scripts": { 15 | "gh-pages": "git checkout master && git pull && git checkout gh-pages && git reset --hard master && git push -f origin gh-pages && git checkout master", 16 | "build:declaration": "rm -rf ./dist/types && tsc --outDir ./dist/types --declaration && rm -rf ./dist/types/*.js", 17 | "build": "npm run build:declaration && NODE_ENV=production rollup -c", 18 | "dev": "BUILD=watch rollup -c -w" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/kopiro/siriwave.git" 23 | }, 24 | "keywords": [ 25 | "siri", 26 | "siriwave", 27 | "ios" 28 | ], 29 | "author": "Flavio De Stefano", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/kopiro/siriwave/issues" 33 | }, 34 | "homepage": "https://github.com/kopiro/siriwave#readme", 35 | "devDependencies": { 36 | "@rollup/plugin-typescript": "^6.1.0", 37 | "eslint": "^7.14.0", 38 | "eslint-plugin-prettier": "^3.1.3", 39 | "prettier": "^2.0.5", 40 | "rollup": "^2.7.6", 41 | "rollup-plugin-livereload": "^2.0.0", 42 | "rollup-plugin-serve": "^1.1.0", 43 | "rollup-plugin-terser": "^7.0.2", 44 | "tslib": "^2.0.0", 45 | "typescript": "^4.1.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import { terser } from "rollup-plugin-terser"; 3 | import livereload from "rollup-plugin-livereload"; 4 | import serve from "rollup-plugin-serve"; 5 | import pkg from "./package.json"; 6 | 7 | const input = "./src/index.ts"; 8 | 9 | const commonTSPluginOptions = { 10 | lib: ["es5", "es6", "ESNext", "dom"], 11 | }; 12 | 13 | const umdConfig = (min, plugins = []) => ({ 14 | input, 15 | output: { 16 | name: pkg.umdName, 17 | file: min ? pkg.browser.replace(".js", ".min.js") : pkg.browser, 18 | exports: "default", 19 | format: "umd", 20 | }, 21 | plugins: [typescript(commonTSPluginOptions), ...(min ? [terser()] : []), ...plugins], 22 | }); 23 | 24 | const esConfig = (min, plugins = []) => ({ 25 | input, 26 | output: { 27 | file: min ? pkg.module.replace(".js", ".min.js") : pkg.module, 28 | format: "es", 29 | }, 30 | plugins: [typescript({ ...commonTSPluginOptions, target: "es6" }), ...(min ? [terser()] : []), ...plugins], 31 | }); 32 | 33 | export default process.env.BUILD === "watch" 34 | ? [ 35 | umdConfig(false, [ 36 | serve({ 37 | open: true, 38 | contentBase: ".", 39 | }), 40 | livereload({ 41 | watch: "dist", 42 | }), 43 | ]), 44 | ] 45 | : [esConfig(true), esConfig(false), umdConfig(true), umdConfig(false)]; 46 | -------------------------------------------------------------------------------- /src/classic-curve.ts: -------------------------------------------------------------------------------- 1 | import SiriWave, { IClassicCurveDefinition, ICurve } from "./index"; 2 | export class ClassicCurve implements ICurve { 3 | ctrl: SiriWave; 4 | definition: IClassicCurveDefinition; 5 | ATT_FACTOR = 4; 6 | GRAPH_X = 2; 7 | AMPLITUDE_FACTOR = 0.6; 8 | 9 | constructor(ctrl: SiriWave, definition: IClassicCurveDefinition) { 10 | this.ctrl = ctrl; 11 | this.definition = definition; 12 | } 13 | 14 | private globalAttFn(x: number): number { 15 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, this.ATT_FACTOR)), this.ATT_FACTOR); 16 | } 17 | 18 | private xPos(i: number): number { 19 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 20 | } 21 | 22 | private yPos(i: number): number { 23 | return ( 24 | this.AMPLITUDE_FACTOR * 25 | (this.globalAttFn(i) * 26 | (this.ctrl.heightMax * this.ctrl.amplitude) * 27 | (1 / this.definition.attenuation) * 28 | Math.sin(this.ctrl.opt.frequency! * i - this.ctrl.phase)) 29 | ); 30 | } 31 | 32 | draw(): void { 33 | const { ctx } = this.ctrl; 34 | 35 | ctx.moveTo(0, 0); 36 | ctx.beginPath(); 37 | 38 | const finalColor = this.definition.color || this.ctrl.color; 39 | const colorHex = finalColor.replace(/rgb\(/g, "").replace(/\)/g, ""); 40 | ctx.strokeStyle = `rgba(${colorHex},${this.definition.opacity})`; 41 | ctx.lineWidth = this.definition.lineWidth; 42 | 43 | // Cycle the graph from -X to +X every PX_DEPTH and draw the line 44 | for (let i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth!) { 45 | ctx.lineTo(this.xPos(i), this.ctrl.heightMax + this.yPos(i)); 46 | } 47 | 48 | ctx.stroke(); 49 | } 50 | 51 | static getDefinition(): IClassicCurveDefinition[] { 52 | return [ 53 | { 54 | attenuation: -2, 55 | lineWidth: 1, 56 | opacity: 0.1, 57 | }, 58 | { 59 | attenuation: -6, 60 | lineWidth: 1, 61 | opacity: 0.2, 62 | }, 63 | { 64 | attenuation: 4, 65 | lineWidth: 1, 66 | opacity: 0.4, 67 | }, 68 | { 69 | attenuation: 2, 70 | lineWidth: 1, 71 | opacity: 0.6, 72 | }, 73 | { 74 | attenuation: 1, 75 | lineWidth: 1.5, 76 | opacity: 1, 77 | }, 78 | ]; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ClassicCurve } from "./classic-curve"; 2 | import { iOS9Curve } from "./ios9-curve"; 3 | 4 | type CurveStyle = "ios" | "ios9"; 5 | 6 | type GlobalCompositeOperation = 7 | | "color" 8 | | "color-burn" 9 | | "color-dodge" 10 | | "copy" 11 | | "darken" 12 | | "destination-atop" 13 | | "destination-in" 14 | | "destination-out" 15 | | "destination-over" 16 | | "difference" 17 | | "exclusion" 18 | | "hard-light" 19 | | "hue" 20 | | "lighten" 21 | | "lighter" 22 | | "luminosity" 23 | | "multiply" 24 | | "overlay" 25 | | "saturation" 26 | | "screen" 27 | | "soft-light" 28 | | "source-atop" 29 | | "source-in" 30 | | "source-out" 31 | | "source-over" 32 | | "xor"; 33 | 34 | export type Options = { 35 | // The DOM container where the DOM canvas element will be added 36 | container: HTMLElement; 37 | // The style of the wave: `ios` or `ios9` 38 | style?: CurveStyle; 39 | // Ratio of the display to use. Calculated by default. 40 | ratio?: number; 41 | // The speed of the animation. 42 | speed?: number; 43 | // The amplitude of the complete wave. 44 | amplitude?: number; 45 | // The frequency for the complete wave (how many waves). - Not available in iOS9 Style 46 | frequency?: number; 47 | // The color of the wave, in hexadecimal form (`#336699`, `#FF0`). - Not available in iOS9 Style 48 | color?: string; 49 | // The `canvas` covers the entire width or height of the container. 50 | cover?: boolean; 51 | // Width of the canvas. Calculated by default. 52 | width?: number; 53 | // Height of the canvas. Calculated by default. 54 | height?: number; 55 | // Decide wether start the animation on boot. 56 | autostart?: boolean; 57 | // Number of step (in pixels) used when drawed on canvas. 58 | pixelDepth?: number; 59 | // Lerp speed to interpolate properties. 60 | lerpSpeed?: number; 61 | // Curve definition override 62 | curveDefinition?: ICurveDefinition[]; 63 | // Ranges of random parameters for the curves - Only available in iOS9 Style 64 | ranges?: IiOS9Ranges; 65 | // Handles overlapping of waves design. - Only available in iOS9 Style 66 | globalCompositeOperation?: GlobalCompositeOperation; 67 | }; 68 | 69 | export type IiOS9CurveDefinition = { 70 | supportLine?: boolean; 71 | color: string; 72 | }; 73 | 74 | // Each wave chooses a random parameter for each of these ranges that factors into their creation. 75 | export type IiOS9Ranges = { 76 | noOfCurves?: [number, number]; 77 | amplitude?: [number, number]; 78 | offset?: [number, number]; 79 | width?: [number, number]; 80 | speed?: [number, number]; 81 | despawnTimeout?: [number, number]; 82 | }; 83 | 84 | export type IClassicCurveDefinition = { 85 | attenuation: number; 86 | lineWidth: number; 87 | opacity: number; 88 | color?: string; 89 | }; 90 | 91 | export type ICurveDefinition = IiOS9CurveDefinition | IClassicCurveDefinition; 92 | 93 | export interface ICurve { 94 | draw: () => void; 95 | } 96 | 97 | export default class SiriWave { 98 | opt: Options; 99 | 100 | // Phase of the wave (passed to Math.sin function) 101 | phase: number = 0; 102 | // Boolean value indicating the the animation is running 103 | run: boolean = false; 104 | // Curves objects to animate 105 | curves: ICurve[] = []; 106 | 107 | speed: number; 108 | amplitude: number; 109 | width: number; 110 | height: number; 111 | heightMax: number; 112 | color: string; 113 | interpolation: { 114 | speed: number | null; 115 | amplitude: number | null; 116 | }; 117 | 118 | canvas: HTMLCanvasElement | null; 119 | ctx: CanvasRenderingContext2D; 120 | 121 | animationFrameId: number | undefined; 122 | timeoutId: ReturnType | undefined; 123 | 124 | constructor({ container, ...rest }: Options) { 125 | const csStyle = window.getComputedStyle(container); 126 | 127 | this.opt = { 128 | container, 129 | style: "ios", 130 | ratio: window.devicePixelRatio || 1, 131 | speed: 0.2, 132 | amplitude: 1, 133 | frequency: 6, 134 | color: "#fff", 135 | cover: false, 136 | width: parseInt(csStyle.width.replace("px", ""), 10), 137 | height: parseInt(csStyle.height.replace("px", ""), 10), 138 | autostart: true, 139 | pixelDepth: 0.02, 140 | lerpSpeed: 0.1, 141 | globalCompositeOperation: "lighter", 142 | ...rest, 143 | }; 144 | 145 | /** 146 | * Actual speed of the animation. Is not safe to change this value directly, use `setSpeed` instead. 147 | */ 148 | this.speed = Number(this.opt.speed); 149 | 150 | /** 151 | * Actual amplitude of the animation. Is not safe to change this value directly, use `setAmplitude` instead. 152 | */ 153 | this.amplitude = Number(this.opt.amplitude); 154 | 155 | /** 156 | * Width of the canvas multiplied by pixel ratio 157 | */ 158 | this.width = Number(this.opt.ratio! * this.opt.width!); 159 | 160 | /** 161 | * Height of the canvas multiplied by pixel ratio 162 | */ 163 | this.height = Number(this.opt.ratio! * this.opt.height!); 164 | 165 | /** 166 | * Maximum height for a single wave 167 | */ 168 | this.heightMax = Number(this.height / 2) - 6; 169 | 170 | /** 171 | * Color of the wave (used in Classic iOS) 172 | */ 173 | this.color = `rgb(${this.hex2rgb(this.opt.color!)})`; 174 | 175 | /** 176 | * An object containing controller variables that need to be interpolated 177 | * to an another value before to be actually changed 178 | */ 179 | this.interpolation = { 180 | speed: this.speed, 181 | amplitude: this.amplitude, 182 | }; 183 | 184 | /** 185 | * Canvas DOM Element where curves will be drawn 186 | */ 187 | this.canvas = document.createElement("canvas"); 188 | 189 | /** 190 | * 2D Context from Canvas 191 | */ 192 | const ctx = this.canvas.getContext("2d"); 193 | if (ctx === null) { 194 | throw new Error("Unable to create 2D Context"); 195 | } 196 | this.ctx = ctx; 197 | 198 | // Set dimensions 199 | this.canvas.width = this.width; 200 | this.canvas.height = this.height; 201 | 202 | // By covering, we ensure the canvas is in the same size of the parent 203 | if (this.opt.cover === true) { 204 | this.canvas.style.width = this.canvas.style.height = "100%"; 205 | } else { 206 | this.canvas.style.width = `${this.width / this.opt.ratio!}px`; 207 | this.canvas.style.height = `${this.height / this.opt.ratio!}px`; 208 | } 209 | 210 | // Instantiate all curves based on the style 211 | switch (this.opt.style) { 212 | case "ios9": 213 | this.curves = ((this.opt.curveDefinition || iOS9Curve.getDefinition()) as IiOS9CurveDefinition[]).map( 214 | (def) => new iOS9Curve(this, def), 215 | ); 216 | break; 217 | 218 | case "ios": 219 | default: 220 | this.curves = ((this.opt.curveDefinition || ClassicCurve.getDefinition()) as IClassicCurveDefinition[]).map( 221 | (def) => new ClassicCurve(this, def), 222 | ); 223 | break; 224 | } 225 | 226 | // Attach to the container 227 | this.opt.container.appendChild(this.canvas); 228 | 229 | // Start the animation 230 | if (this.opt.autostart) { 231 | this.start(); 232 | } 233 | } 234 | 235 | /** 236 | * Convert an HEX color to RGB 237 | */ 238 | private hex2rgb(hex: string): string | null { 239 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 240 | hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); 241 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 242 | return result 243 | ? `${parseInt(result[1], 16).toString()},${parseInt(result[2], 16).toString()},${parseInt( 244 | result[3], 245 | 16, 246 | ).toString()}` 247 | : null; 248 | } 249 | 250 | private intLerp(v0: number, v1: number, t: number): number { 251 | return v0 * (1 - t) + v1 * t; 252 | } 253 | 254 | /** 255 | * Interpolate a property to the value found in this.interpolation 256 | */ 257 | private lerp(propertyStr: "amplitude" | "speed"): number | null { 258 | const prop = this.interpolation[propertyStr]; 259 | if (prop !== null) { 260 | this[propertyStr] = this.intLerp(this[propertyStr], prop, this.opt.lerpSpeed!); 261 | if (this[propertyStr] - prop === 0) { 262 | this.interpolation[propertyStr] = null; 263 | } 264 | } 265 | return this[propertyStr]; 266 | } 267 | 268 | /** 269 | * Clear the canvas 270 | */ 271 | private clear() { 272 | this.ctx.globalCompositeOperation = "destination-out"; 273 | this.ctx.fillRect(0, 0, this.width, this.height); 274 | this.ctx.globalCompositeOperation = "source-over"; 275 | } 276 | 277 | /** 278 | * Draw all curves 279 | */ 280 | private draw() { 281 | this.curves.forEach((curve) => curve.draw()); 282 | } 283 | 284 | /** 285 | * Clear the space, interpolate values, calculate new steps and draws 286 | * @returns 287 | */ 288 | private startDrawCycle() { 289 | this.clear(); 290 | 291 | // Interpolate values 292 | this.lerp("amplitude"); 293 | this.lerp("speed"); 294 | 295 | this.draw(); 296 | this.phase = (this.phase + (Math.PI / 2) * this.speed) % (2 * Math.PI); 297 | 298 | if (window.requestAnimationFrame) { 299 | this.animationFrameId = window.requestAnimationFrame(this.startDrawCycle.bind(this)); 300 | } else { 301 | this.timeoutId = setTimeout(this.startDrawCycle.bind(this), 20); 302 | } 303 | } 304 | 305 | /* API */ 306 | 307 | /** 308 | * Start the animation 309 | */ 310 | start() { 311 | if (!this.canvas) { 312 | throw new Error("This instance of SiriWave has been disposed, please create a new instance"); 313 | } 314 | 315 | this.phase = 0; 316 | 317 | // Ensure we don't re-launch the draw cycle 318 | if (!this.run) { 319 | this.run = true; 320 | this.startDrawCycle(); 321 | } 322 | } 323 | 324 | /** 325 | * Stop the animation 326 | */ 327 | stop() { 328 | this.phase = 0; 329 | this.run = false; 330 | 331 | // Clear old draw cycle on stop 332 | if (this.animationFrameId) { 333 | window.cancelAnimationFrame(this.animationFrameId); 334 | } 335 | if (this.timeoutId) { 336 | clearTimeout(this.timeoutId); 337 | } 338 | } 339 | 340 | /** 341 | * Dispose 342 | */ 343 | dispose() { 344 | this.stop(); 345 | if (this.canvas) { 346 | this.canvas.remove(); 347 | this.canvas = null; 348 | } 349 | } 350 | 351 | /** 352 | * Set a new value for a property (interpolated) 353 | */ 354 | set(propertyStr: "amplitude" | "speed", value: number) { 355 | this.interpolation[propertyStr] = value; 356 | } 357 | 358 | /** 359 | * Set a new value for the speed property (interpolated) 360 | */ 361 | setSpeed(value: number) { 362 | this.set("speed", value); 363 | } 364 | 365 | /** 366 | * Set a new value for the amplitude property (interpolated) 367 | */ 368 | setAmplitude(value: number) { 369 | this.set("amplitude", value); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/ios9-curve.ts: -------------------------------------------------------------------------------- 1 | import SiriWave, { ICurve, IiOS9CurveDefinition } from "./index"; 2 | 3 | export class iOS9Curve implements ICurve { 4 | ctrl: SiriWave; 5 | definition: IiOS9CurveDefinition; 6 | 7 | spawnAt: number; 8 | noOfCurves: number; 9 | prevMaxY: number; 10 | 11 | phases: number[]; 12 | amplitudes: number[]; 13 | despawnTimeouts: number[]; 14 | offsets: number[]; 15 | speeds: number[]; 16 | finalAmplitudes: number[]; 17 | widths: number[]; 18 | verses: number[]; 19 | 20 | GRAPH_X = 25; 21 | AMPLITUDE_FACTOR = 0.8; 22 | SPEED_FACTOR = 1; 23 | DEAD_PX = 2; 24 | ATT_FACTOR = 4; 25 | 26 | DESPAWN_FACTOR = 0.02; 27 | 28 | DEFAULT_NOOFCURVES_RANGES: [number, number] = [2, 5]; 29 | DEFAULT_AMPLITUDE_RANGES: [number, number] = [0.3, 1]; 30 | DEFAULT_OFFSET_RANGES: [number, number] = [-3, 3]; 31 | DEFAULT_WIDTH_RANGES: [number, number] = [1, 3]; 32 | DEFAULT_SPEED_RANGES: [number, number] = [0.5, 1]; 33 | DEFAULT_DESPAWN_TIMEOUT_RANGES: [number, number] = [500, 2000]; 34 | 35 | constructor(ctrl: SiriWave, definition: IiOS9CurveDefinition) { 36 | this.ctrl = ctrl; 37 | this.definition = definition; 38 | 39 | this.noOfCurves = 0; 40 | this.spawnAt = 0; 41 | this.prevMaxY = 0; 42 | 43 | this.phases = []; 44 | this.offsets = []; 45 | this.speeds = []; 46 | this.finalAmplitudes = []; 47 | this.widths = []; 48 | this.amplitudes = []; 49 | this.despawnTimeouts = []; 50 | this.verses = []; 51 | } 52 | 53 | private getRandomRange(e: [number, number]): number { 54 | return e[0] + Math.random() * (e[1] - e[0]); 55 | } 56 | 57 | private spawnSingle(ci: number): void { 58 | this.phases[ci] = 0; 59 | this.amplitudes[ci] = 0; 60 | 61 | this.despawnTimeouts[ci] = this.getRandomRange( 62 | this.ctrl.opt.ranges?.despawnTimeout ?? this.DEFAULT_DESPAWN_TIMEOUT_RANGES, 63 | ); 64 | this.offsets[ci] = this.getRandomRange(this.ctrl.opt.ranges?.offset ?? this.DEFAULT_OFFSET_RANGES); 65 | this.speeds[ci] = this.getRandomRange(this.ctrl.opt.ranges?.speed ?? this.DEFAULT_SPEED_RANGES); 66 | this.finalAmplitudes[ci] = this.getRandomRange(this.ctrl.opt.ranges?.amplitude ?? this.DEFAULT_AMPLITUDE_RANGES); 67 | this.widths[ci] = this.getRandomRange(this.ctrl.opt.ranges?.width ?? this.DEFAULT_WIDTH_RANGES); 68 | this.verses[ci] = this.getRandomRange([-1, 1]); 69 | } 70 | 71 | private getEmptyArray(count: number): number[] { 72 | return new Array(count); 73 | } 74 | 75 | private spawn(): void { 76 | this.spawnAt = Date.now(); 77 | 78 | this.noOfCurves = Math.floor( 79 | this.getRandomRange(this.ctrl.opt.ranges?.noOfCurves ?? this.DEFAULT_NOOFCURVES_RANGES), 80 | ); 81 | 82 | this.phases = this.getEmptyArray(this.noOfCurves); 83 | this.offsets = this.getEmptyArray(this.noOfCurves); 84 | this.speeds = this.getEmptyArray(this.noOfCurves); 85 | this.finalAmplitudes = this.getEmptyArray(this.noOfCurves); 86 | this.widths = this.getEmptyArray(this.noOfCurves); 87 | this.amplitudes = this.getEmptyArray(this.noOfCurves); 88 | this.despawnTimeouts = this.getEmptyArray(this.noOfCurves); 89 | this.verses = this.getEmptyArray(this.noOfCurves); 90 | 91 | for (let ci = 0; ci < this.noOfCurves; ci++) { 92 | this.spawnSingle(ci); 93 | } 94 | } 95 | 96 | private globalAttFn(x: number): number { 97 | return Math.pow(this.ATT_FACTOR / (this.ATT_FACTOR + Math.pow(x, 2)), this.ATT_FACTOR); 98 | } 99 | 100 | private sin(x: number, phase: number): number { 101 | return Math.sin(x - phase); 102 | } 103 | 104 | private yRelativePos(i: number): number { 105 | let y = 0; 106 | 107 | for (let ci = 0; ci < this.noOfCurves; ci++) { 108 | // Generate a static T so that each curve is distant from each oterh 109 | let t = 4 * (-1 + (ci / (this.noOfCurves - 1)) * 2); 110 | // but add a dynamic offset 111 | t += this.offsets![ci]; 112 | 113 | const k = 1 / this.widths![ci]; 114 | const x = i * k - t; 115 | 116 | y += Math.abs(this.amplitudes[ci] * this.sin(this.verses[ci] * x, this.phases[ci]) * this.globalAttFn(x)); 117 | } 118 | 119 | // Divide for NoOfCurves so that y <= 1 120 | return y / this.noOfCurves; 121 | } 122 | 123 | private yPos(i: number): number { 124 | return ( 125 | this.AMPLITUDE_FACTOR * 126 | this.ctrl.heightMax * 127 | this.ctrl.amplitude * 128 | this.yRelativePos(i) * 129 | this.globalAttFn((i / this.GRAPH_X) * 2) 130 | ); 131 | } 132 | 133 | private xPos(i: number): number { 134 | return this.ctrl.width * ((i + this.GRAPH_X) / (this.GRAPH_X * 2)); 135 | } 136 | 137 | private drawSupportLine() { 138 | const { ctx } = this.ctrl; 139 | 140 | const coo: [number, number, number, number] = [0, this.ctrl.heightMax, this.ctrl.width, 1]; 141 | const gradient = ctx.createLinearGradient.apply(ctx, coo); 142 | gradient.addColorStop(0, "transparent"); 143 | gradient.addColorStop(0.1, "rgba(255,255,255,.5)"); 144 | gradient.addColorStop(1 - 0.1 - 0.1, "rgba(255,255,255,.5)"); 145 | gradient.addColorStop(1, "transparent"); 146 | 147 | ctx.fillStyle = gradient; 148 | ctx.fillRect.apply(ctx, coo); 149 | } 150 | 151 | draw() { 152 | const { ctx } = this.ctrl; 153 | 154 | ctx.globalAlpha = 0.7; 155 | ctx.globalCompositeOperation = this.ctrl.opt.globalCompositeOperation!; 156 | 157 | if (this.spawnAt === 0) { 158 | this.spawn(); 159 | } 160 | 161 | if (this.definition.supportLine) { 162 | // Draw the support line 163 | return this.drawSupportLine(); 164 | } 165 | 166 | for (let ci = 0; ci < this.noOfCurves; ci++) { 167 | if (this.spawnAt + this.despawnTimeouts[ci] <= Date.now()) { 168 | this.amplitudes[ci] -= this.DESPAWN_FACTOR; 169 | } else { 170 | this.amplitudes[ci] += this.DESPAWN_FACTOR; 171 | } 172 | 173 | this.amplitudes[ci] = Math.min(Math.max(this.amplitudes[ci], 0), this.finalAmplitudes[ci]); 174 | this.phases[ci] = (this.phases[ci] + this.ctrl.speed * this.speeds[ci] * this.SPEED_FACTOR) % (2 * Math.PI); 175 | } 176 | 177 | let maxY = -Infinity; 178 | let minX = +Infinity; 179 | 180 | // Write two opposite waves 181 | for (const sign of [1, -1]) { 182 | ctx.beginPath(); 183 | 184 | for (let i = -this.GRAPH_X; i <= this.GRAPH_X; i += this.ctrl.opt.pixelDepth!) { 185 | const x = this.xPos(i); 186 | const y = this.yPos(i); 187 | ctx.lineTo(x, this.ctrl.heightMax - sign * y); 188 | 189 | minX = Math.min(minX, x); 190 | maxY = Math.max(maxY, y); 191 | } 192 | 193 | ctx.closePath(); 194 | 195 | ctx.fillStyle = `rgba(${this.definition.color}, 1)`; 196 | ctx.strokeStyle = `rgba(${this.definition.color}, 1)`; 197 | ctx.fill(); 198 | } 199 | 200 | if (maxY < this.DEAD_PX && this.prevMaxY > maxY) { 201 | this.spawnAt = 0; 202 | } 203 | 204 | this.prevMaxY = maxY; 205 | 206 | return null; 207 | } 208 | 209 | static getDefinition(): IiOS9CurveDefinition[] { 210 | return [ 211 | { 212 | color: "255,255,255", 213 | supportLine: true, 214 | }, 215 | { 216 | // blue 217 | color: "15, 82, 169", 218 | }, 219 | { 220 | // red 221 | color: "173, 57, 76", 222 | }, 223 | { 224 | // green 225 | color: "48, 220, 155", 226 | }, 227 | ]; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "module": "es6", 5 | "target": "es5", 6 | "strict": true 7 | }, 8 | "include": ["./src/**/*"], 9 | "exclude": ["./node_modules"] 10 | } 11 | --------------------------------------------------------------------------------