├── www ├── dynamowaves.d.ts ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-70x70.png ├── apple-touch-icon.png ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── browserconfig.xml ├── site.webmanifest ├── snippets │ ├── installation │ │ ├── npm.md │ │ ├── script.md │ │ ├── installation-header.md │ │ └── angular.md │ ├── usage │ │ ├── overview.md │ │ ├── functions.md │ │ └── data-attributes.md │ ├── snippets-index.json │ └── practical-application │ │ └── examples.md ├── LICENSE ├── safari-pinned-tab.svg ├── README.md ├── main.css.map ├── dynamowaves.min.js ├── normalize.css ├── main.css └── main.scss ├── .DS_Store ├── .gitignore ├── rollup.config.js ├── package.json ├── dist ├── dynamowaves.d.ts ├── dynamowaves.min.js └── dynamowaves.js ├── test └── dynamowaves.test.js ├── src ├── dynamowaves.d.ts └── dynamowaves.js ├── README.md └── www-src └── index.html /www/dynamowaves.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../src/dynamowaves'; -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/.DS_Store -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/favicon.ico -------------------------------------------------------------------------------- /www/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/favicon-16x16.png -------------------------------------------------------------------------------- /www/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/favicon-32x32.png -------------------------------------------------------------------------------- /www/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/mstile-70x70.png -------------------------------------------------------------------------------- /www/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/apple-touch-icon.png -------------------------------------------------------------------------------- /www/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/mstile-144x144.png -------------------------------------------------------------------------------- /www/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/mstile-150x150.png -------------------------------------------------------------------------------- /www/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/mstile-310x150.png -------------------------------------------------------------------------------- /www/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/mstile-310x310.png -------------------------------------------------------------------------------- /www/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/android-chrome-192x192.png -------------------------------------------------------------------------------- /www/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzebley/dynamowaves/HEAD/www/android-chrome-512x512.png -------------------------------------------------------------------------------- /www/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #e4ecec 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /www/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /www/snippets/installation/npm.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: npm-installation 3 | title: npm 4 | type: docs 5 | group: installation 6 | order: 2 7 | groupOrder: 1 8 | groupLabel: Installation 9 | --- 10 | 11 |

npm Installation

12 | 13 | To install **Dynamowaves** via npm, run the following command: 14 | 15 | ```bash 16 | npm install dynamowaves 17 | ``` 18 | 19 | After installation, you can import **Dynamowaves** in your JavaScript or TypeScript files: 20 | 21 | ```javascript 22 | import 'dynamowaves'; 23 | ``` 24 | -------------------------------------------------------------------------------- /www/snippets/installation/script.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: script-installation 3 | title: Direct Script 4 | type: docs 5 | group: installation 6 | order: 4 7 | groupOrder: 1 8 | groupLabel: Installation 9 | --- 10 | 11 |

Script Installation

12 | 13 | Alternatively, you can include the **Dynamowaves** script file directly in your project: 14 | 15 | ```html 16 | 17 | 18 | 19 | 20 | 21 | ``` 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /www/snippets/installation/installation-header.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: installation-header 3 | title: Overview 4 | type: docs 5 | group: installation 6 | order: 1 7 | groupOrder: 1 8 | groupLabel: Installation 9 | --- 10 | 11 |

Installation

12 | 13 | To make render times functionally instant, **Dynamowaves** intentionally eskews importing a library such as **SVG.js** to build a new SVG on execution. 14 | 15 | Instead, it builds randomly seeded **``````** based on criteria you set, 16 | and then leverages HTML web components (sorry, IE) to allow it to easily grab its reference element's applied attributes and then fully replace it with a slick lil' wave. 17 | 18 | You can install **Dynamowaves** via npm, leveraging a CDN, or by including the script file directly in your project. 19 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | import dts from 'rollup-plugin-dts'; 3 | 4 | export default [ 5 | // Main JavaScript bundle configuration 6 | { 7 | input: 'src/dynamowaves.js', 8 | output: [ 9 | { 10 | file: 'dist/dynamowaves.js', 11 | format: 'umd', 12 | name: 'Dynamowaves', 13 | }, 14 | { 15 | file: 'dist/dynamowaves.min.js', 16 | format: 'umd', 17 | name: 'Dynamowaves', 18 | plugins: [terser()], 19 | }, 20 | { 21 | file: 'www/dynamowaves.min.js', 22 | format: 'umd', 23 | name: 'Dynamowaves', 24 | plugins: [terser()], 25 | }, 26 | ], 27 | }, 28 | // Types bundle configuration 29 | { 30 | input: './src/dynamowaves.d.ts', 31 | output: [ 32 | { 33 | file: 'dist/dynamowaves.d.ts', 34 | format: 'es', 35 | }, 36 | ], 37 | plugins: [dts()], 38 | }, 39 | ]; -------------------------------------------------------------------------------- /www/snippets/installation/angular.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: angular-installation 3 | title: Angular 4 | type: docs 5 | group: installation 6 | order: 3 7 | groupOrder: 1 8 | groupLabel: Installation 9 | --- 10 | 11 |

Angular Setup

12 | 13 | To use **Dynamowaves** in an Angular project, follow these additional steps after installing via npm: 14 | 15 | 1. In your angular.json file, add the dynamowaves script to the scripts array: 16 | 17 | ```json 18 | "scripts": [ 19 | "node_modules/dynamowaves/dist/dynamowaves.js" 20 | ] 21 | ``` 22 | 23 | 2. In your app.module.ts file, add CUSTOM_ELEMENTS_SCHEMA to the schemas array: 24 | 25 | ```typescript 26 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 27 | 28 | @NgModule({ 29 | // ... 30 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 31 | }) 32 | 33 | export class AppModule { } 34 | ``` 35 | -------------------------------------------------------------------------------- /www/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mark Zebley 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 | -------------------------------------------------------------------------------- /www/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /www/snippets/usage/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: usage-header 3 | title: Overview 4 | type: docs 5 | group: usage 6 | order: 1 7 | groupOrder: 2 8 | groupLabel: Usage 9 | --- 10 | 11 |

Usage

12 | 13 | Since **Dynamowaves** use HTML templating syntax, all it takes to call one is to add the custom element to your HTML! 14 | 15 | ```html 16 | 17 | 18 | ``` 19 | 20 | 21 | 22 | A **dynamo-wave** will inherit any **class**, **id**, or **style** applied to its invoking element. 23 | 24 | ```html 25 | 26 | 27 | ``` 28 | 29 | 30 | 31 | ```html 32 | 37 | 38 | 39 | 40 | ``` 41 | 42 | 43 | 44 | ```html 45 | 52 | 53 | 54 | 55 | ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | # Authoring docs with mark↓ 2 | 3 | The marketing site uses the **mark↓** toolchain to render documentation from Markdown files. 4 | Content is stored statically and compiled into final HTML before deployment. 5 | 6 | ## Content locations 7 | - **Markdown snippets**: `www/snippets/` 8 | - **Docs template page**: `www-src/index.html` 9 | - **Compiled output**: `www/index.html` 10 | 11 | ## Writing snippets 12 | 1. Add `.md` files under `www/snippets/`. 13 | 2. Include front matter: 14 | 15 | ```markdown 16 | --- 17 | slug: usage-intro 18 | title: Usage intro 19 | order: 2 20 | --- 21 | 22 |

Markdown supports inline HTML.

23 | ``` 24 | 25 | 3. Write standard Markdown or HTML. 26 | 27 | ## How docs are built 28 | Two phases: 29 | 30 | 1. **Manifest generation** → `www/snippets-index.json` 31 | 2. **Page compilation** → consumes manifest + `www-src/index.html` → outputs `www/index.html` 32 | 33 | ## Commands 34 | 35 | ### Build once 36 | - `npm run docs:manifest` 37 | - `npm run docs:build` 38 | 39 | ### Watch while editing 40 | - `npm run docs:watch` 41 | 42 | Pairs well with: 43 | 44 | ```bash 45 | npx serve www 46 | ``` 47 | 48 | ## CDN client-side hydration (optional) 49 | 50 | ```html 51 | 56 | ``` 57 | 58 | ## Deployment 59 | Commit: 60 | - Markdown files 61 | - `www/snippets-index.json` 62 | - Compiled `www/index.html` 63 | 64 | Everything in `www/` is static and production‑ready. 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamowaves", 3 | "version": "2.1.1", 4 | "type": "module", 5 | "description": "Lightweight, dependency-free SVG wave templates that dynamically generate themselves on render.", 6 | "main": "dist/dynamowaves.js", 7 | "module": "dist/dynamowaves.js", 8 | "types": "dist/dynamowaves.d.ts", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "test": "node --test", 14 | "build": "rollup -c", 15 | "prepublishOnly": "npm run build", 16 | "release": "npm publish", 17 | "release:dry": "npm run build && npm pack --dry-run", 18 | "docs:manifest": "mark-down build www/snippets --output www/snippets/snippets-index.json", 19 | "docs:manifest:watch": "mark-down watch www/snippets --output www/snippets/snippets-index.json", 20 | "docs:build": "npm run docs:manifest && mark-down compile-page www-src/index.html --manifest www/snippets/snippets-index.json --outDir www", 21 | "docs:watch": "chokidar \"www-src/index.html\" \"www/snippets/**/*.md\" -c \"npm run docs:build\"" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/mzebley/dynamowaves.git" 26 | }, 27 | "keywords": [ 28 | "javascript", 29 | "svg", 30 | "html", 31 | "waves", 32 | "wave", 33 | "generative", 34 | "templates-html" 35 | ], 36 | "author": "Mark Zebley", 37 | "license": "ISC", 38 | "bugs": { 39 | "url": "https://github.com/mzebley/dynamowaves/issues" 40 | }, 41 | "homepage": "https://dynamowaves.markzebley.com/", 42 | "devDependencies": { 43 | "@mzebley/mark-down-cli": "^1.2.2", 44 | "@rollup/plugin-terser": "^0.4.4", 45 | "chokidar-cli": "^3.0.0", 46 | "rollup": "^3.29.4", 47 | "rollup-plugin-dts": "^6.1.1" 48 | } 49 | } -------------------------------------------------------------------------------- /www/snippets/usage/functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: usage-functions 3 | title: Functions 4 | type: docs 5 | group: usage 6 | order: 3 7 | groupOrder: 2 8 | groupLabel: Usage 9 | --- 10 | 11 |

Available Functions

12 | 13 | **Dynamowaves** come with a few functions that can be called to manipulate the wave after it's been rendered. 14 | 15 |

.generateNewWave()

16 | 17 | Want to see a new wave? Call **generateNewWave(duration)** on the **dynamowave** you'd like to regenerate. The **duration** parameter is an optional integer that determines how quickly the old wave morphs into the new wave - default is **800**(ms). 18 | 19 | ```javascript 20 | const wave = document.querySelector('dynamo-wave'); 21 | wave.generateNewWave(500); 22 | ``` 23 | 24 | 25 | 26 | 27 | 28 |

.play()

29 | 30 | Call **play(duration)** on any **dynamowave** that you'd like to animate. The **duration** parameter is an optional integer that determines the length of the animation loop - default is **7500**(ms). 31 | 32 | 33 |

.pause()

34 | 35 | To stop the animation loop, call **pause()** on the **dynamowave** you'd like to stop. 36 | 37 | ```javascript 38 | const wave = document.querySelector('dynamo-wave'); 39 | 40 | function toggleWaveAnimation() { 41 | if (wave.isAnimating) { 42 | wave.pause(); 43 | } else { 44 | wave.play(5000); 45 | } 46 | } 47 | ``` 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /www/snippets/snippets-index.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "slug": "examples", 4 | "title": "Examples", 5 | "order": 1, 6 | "type": "docs", 7 | "path": "practical-application/examples.md", 8 | "group": "practical-application", 9 | "extra": { 10 | "group": "practical-application", 11 | "groupOrder": 3, 12 | "groupLabel": "Practical Application" 13 | } 14 | }, 15 | { 16 | "slug": "installation-header", 17 | "title": "Overview", 18 | "order": 1, 19 | "type": "docs", 20 | "path": "installation/installation-header.md", 21 | "group": "installation", 22 | "extra": { 23 | "group": "installation", 24 | "groupOrder": 1, 25 | "groupLabel": "Installation" 26 | } 27 | }, 28 | { 29 | "slug": "usage-header", 30 | "title": "Overview", 31 | "order": 1, 32 | "type": "docs", 33 | "path": "usage/overview.md", 34 | "group": "usage", 35 | "extra": { 36 | "group": "usage", 37 | "groupOrder": 2, 38 | "groupLabel": "Usage" 39 | } 40 | }, 41 | { 42 | "slug": "data-attributes", 43 | "title": "Data Attributes", 44 | "order": 2, 45 | "type": "docs", 46 | "path": "usage/data-attributes.md", 47 | "group": "usage", 48 | "extra": { 49 | "group": "usage", 50 | "groupOrder": 2, 51 | "groupLabel": "Usage" 52 | } 53 | }, 54 | { 55 | "slug": "npm-installation", 56 | "title": "npm", 57 | "order": 2, 58 | "type": "docs", 59 | "path": "installation/npm.md", 60 | "group": "installation", 61 | "extra": { 62 | "group": "installation", 63 | "groupOrder": 1, 64 | "groupLabel": "Installation" 65 | } 66 | }, 67 | { 68 | "slug": "angular-installation", 69 | "title": "Angular", 70 | "order": 3, 71 | "type": "docs", 72 | "path": "installation/angular.md", 73 | "group": "installation", 74 | "extra": { 75 | "group": "installation", 76 | "groupOrder": 1, 77 | "groupLabel": "Installation" 78 | } 79 | }, 80 | { 81 | "slug": "usage-functions", 82 | "title": "Functions", 83 | "order": 3, 84 | "type": "docs", 85 | "path": "usage/functions.md", 86 | "group": "usage", 87 | "extra": { 88 | "group": "usage", 89 | "groupOrder": 2, 90 | "groupLabel": "Usage" 91 | } 92 | }, 93 | { 94 | "slug": "script-installation", 95 | "title": "Direct Script", 96 | "order": 4, 97 | "type": "docs", 98 | "path": "installation/script.md", 99 | "group": "installation", 100 | "extra": { 101 | "group": "installation", 102 | "groupOrder": 1, 103 | "groupLabel": "Installation" 104 | } 105 | } 106 | ] -------------------------------------------------------------------------------- /dist/dynamowaves.d.ts: -------------------------------------------------------------------------------- 1 | // dynamoves.d.ts 2 | 3 | // Interface for wave generation options 4 | interface WaveGenerationOptions { 5 | width: number; 6 | height: number; 7 | points: number; 8 | variance: number; 9 | vertical?: boolean; 10 | random?: () => number; 11 | startEndZero?: boolean; 12 | } 13 | 14 | // Interface for wave point structure 15 | interface WavePoint { 16 | cpX: number; 17 | cpY: number; 18 | x: number; 19 | y: number; 20 | } 21 | 22 | // Type for the wave direction 23 | type WaveDirection = 'top' | 'bottom' | 'left' | 'right'; 24 | 25 | // Interface for intersection observer options 26 | interface WaveObserverOptions { 27 | root: Element | null; 28 | rootMargin: string; 29 | threshold: number; 30 | } 31 | 32 | declare class DynamoWave extends HTMLElement { 33 | // Properties 34 | private isAnimating: boolean; 35 | private animationFrameId: number | null; 36 | private elapsedTime: number; 37 | private startTime: number | null; 38 | private isGeneratingWave: boolean; 39 | private currentPath: string | null; 40 | private targetPath: string | null; 41 | private pendingTargetPath: string | null; 42 | private intersectionObserver: IntersectionObserver | null; 43 | private observerOptions: WaveObserverOptions | null; 44 | private points: number; 45 | private variance: number; 46 | private duration: number; 47 | private vertical: boolean; 48 | private width: number; 49 | private height: number; 50 | private svg: SVGSVGElement; 51 | private path: SVGPathElement; 52 | private random: () => number; 53 | private startEndZero: boolean; 54 | 55 | constructor(); 56 | 57 | // Lifecycle methods 58 | connectedCallback(): void; 59 | disconnectedCallback(): void; 60 | 61 | // Public methods 62 | play(customDuration?: number | null): void; 63 | pause(): void; 64 | generateNewWave(duration?: number): void; 65 | 66 | // Private methods 67 | private updateSeedAttribute(pathString: string): void; 68 | private setupIntersectionObserver(observeConfig: string): void; 69 | private animateWave(duration: number, onComplete?: (() => void) | null): void; 70 | } 71 | declare function encodeWaveSeed(pathString: string): string; 72 | declare function decodeWaveSeed(seed: string): string | null; 73 | 74 | // Global declaration for custom element 75 | declare global { 76 | interface HTMLElementTagNameMap { 77 | 'dynamo-wave': DynamoWave; 78 | } 79 | } 80 | 81 | // Component attributes interface 82 | interface DynamoWaveAttributes { 83 | 'data-wave-face'?: WaveDirection; 84 | 'data-wave-points'?: string; 85 | 'data-wave-variance'?: string; 86 | 'data-variance'?: string; 87 | 'data-wave-speed'?: string; 88 | 'data-wave-animate'?: string; 89 | 'data-wave-observe'?: string; 90 | 'data-wave-seed'?: string; 91 | 'data-start-end-zero'?: string; 92 | } 93 | 94 | // Extend HTMLElement interface to include our attributes 95 | declare global { 96 | interface HTMLElementTagNameMap { 97 | 'dynamo-wave': DynamoWave; 98 | } 99 | 100 | namespace JSX { 101 | interface IntrinsicElements { 102 | 'dynamo-wave': Partial; 103 | } 104 | } 105 | } 106 | 107 | export { DynamoWave, type DynamoWaveAttributes, type WaveDirection, type WaveGenerationOptions, type WaveObserverOptions, type WavePoint, decodeWaveSeed, encodeWaveSeed }; 108 | -------------------------------------------------------------------------------- /test/dynamowaves.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict'; 2 | import { before, describe, it } from 'node:test'; 3 | 4 | typeof globalThis.HTMLElement === 'undefined' && (globalThis.HTMLElement = class {}); 5 | 6 | typeof globalThis.customElements === 'undefined' && 7 | (globalThis.customElements = { 8 | registry: new Map(), 9 | get(name) { 10 | return this.registry.get(name); 11 | }, 12 | define(name, ctor) { 13 | this.registry.set(name, ctor); 14 | }, 15 | }); 16 | 17 | let generateWave; 18 | let parsePath; 19 | let interpolateWave; 20 | let encodeWaveSeed; 21 | let decodeWaveSeed; 22 | 23 | before(async () => { 24 | ({ generateWave, parsePath, interpolateWave, encodeWaveSeed, decodeWaveSeed } = await import('../src/dynamowaves.js')); 25 | }); 26 | 27 | describe('generateWave', () => { 28 | it('creates a horizontal path with start and end anchored to height when requested', () => { 29 | const path = generateWave({ 30 | width: 100, 31 | height: 50, 32 | points: 3, 33 | variance: 1, 34 | random: () => 0, 35 | startEndZero: true, 36 | }); 37 | 38 | assert.ok(path.startsWith('M 0 50 L 0 50')); 39 | assert.ok(path.includes('Q 0 50, 25 47.5')); 40 | assert.ok(path.includes('Q 50 45, 75 47.5')); 41 | assert.ok(path.endsWith('Q 100 50, 100 50 L 100 50 Z')); 42 | }); 43 | 44 | it('supports vertical waves while respecting start and end anchors', () => { 45 | const path = generateWave({ 46 | width: 20, 47 | height: 10, 48 | points: 2, 49 | variance: 1, 50 | vertical: true, 51 | random: () => 0, 52 | startEndZero: true, 53 | }); 54 | 55 | assert.ok(path.startsWith('M 20 10 L 20 10')); 56 | assert.ok(path.includes('Q 20 10, 20 5')); 57 | assert.ok(path.endsWith('Q 20 0, 0 0 L 20 0 L 20 10 Z')); 58 | }); 59 | }); 60 | 61 | describe('parsePath', () => { 62 | it('extracts control points and endpoints from quadratic commands', () => { 63 | const path = 'M 0 50 L 0 40 Q 10 20, 20 10 Q 30 5, 40 0 L 100 0 Z'; 64 | const points = parsePath(path); 65 | 66 | assert.deepEqual(points, [ 67 | { cpX: 10, cpY: 20, x: 20, y: 10 }, 68 | { cpX: 30, cpY: 5, x: 40, y: 0 }, 69 | ]); 70 | }); 71 | }); 72 | 73 | describe('interpolateWave', () => { 74 | it('interpolates between point sets', () => { 75 | const current = [ 76 | { cpX: 0, cpY: 10, x: 20, y: 30 }, 77 | { cpX: 40, cpY: 20, x: 60, y: 40 }, 78 | ]; 79 | const target = [ 80 | { cpX: 10, cpY: 20, x: 30, y: 50 }, 81 | { cpX: 50, cpY: 30, x: 70, y: 60 }, 82 | ]; 83 | 84 | const path = interpolateWave(current, target, 0.5, false, 80, 100); 85 | 86 | const expected = 'M 0 80 L 0 40 Q 5 15, 20 40 Q 45 25, 60 50 L 100 80 Z'; 87 | assert.equal(path, expected); 88 | }); 89 | }); 90 | 91 | describe('wave seed encoding', () => { 92 | it('round-trips seeds even with non-breaking spaces removed', () => { 93 | const basePath = 'M 0 80 L 0 40 Q 5 15, 25 40'; 94 | const encoded = encodeWaveSeed(basePath); 95 | const decoded = decodeWaveSeed(encoded); 96 | 97 | assert.equal(decoded, 'M 0 80 L 0 40 Q 5 15, 25 40'); 98 | }); 99 | 100 | it('returns null for invalid input', () => { 101 | assert.equal(decodeWaveSeed(''), null); 102 | assert.equal(decodeWaveSeed('%%%'), null); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /src/dynamowaves.d.ts: -------------------------------------------------------------------------------- 1 | // dynamoves.d.ts 2 | 3 | // Interface for wave generation options 4 | interface WaveGenerationOptions { 5 | width: number; 6 | height: number; 7 | points: number; 8 | variance: number; 9 | vertical?: boolean; 10 | random?: () => number; 11 | startEndZero?: boolean; 12 | } 13 | 14 | // Interface for wave point structure 15 | interface WavePoint { 16 | cpX: number; 17 | cpY: number; 18 | x: number; 19 | y: number; 20 | } 21 | 22 | // Type for the wave direction 23 | type WaveDirection = 'top' | 'bottom' | 'left' | 'right'; 24 | 25 | // Interface for intersection observer options 26 | interface WaveObserverOptions { 27 | root: Element | null; 28 | rootMargin: string; 29 | threshold: number; 30 | } 31 | 32 | declare class DynamoWave extends HTMLElement { 33 | // Properties 34 | private isAnimating: boolean; 35 | private animationFrameId: number | null; 36 | private elapsedTime: number; 37 | private startTime: number | null; 38 | private isGeneratingWave: boolean; 39 | private currentPath: string | null; 40 | private targetPath: string | null; 41 | private pendingTargetPath: string | null; 42 | private intersectionObserver: IntersectionObserver | null; 43 | private observerOptions: WaveObserverOptions | null; 44 | private points: number; 45 | private variance: number; 46 | private duration: number; 47 | private vertical: boolean; 48 | private width: number; 49 | private height: number; 50 | private svg: SVGSVGElement; 51 | private path: SVGPathElement; 52 | private random: () => number; 53 | private startEndZero: boolean; 54 | 55 | constructor(); 56 | 57 | // Lifecycle methods 58 | connectedCallback(): void; 59 | disconnectedCallback(): void; 60 | 61 | // Public methods 62 | play(customDuration?: number | null): void; 63 | pause(): void; 64 | generateNewWave(duration?: number): void; 65 | 66 | // Private methods 67 | private updateSeedAttribute(pathString: string): void; 68 | private setupIntersectionObserver(observeConfig: string): void; 69 | private animateWave(duration: number, onComplete?: (() => void) | null): void; 70 | } 71 | 72 | // Helper function declarations 73 | declare function generateWave(options: WaveGenerationOptions): string; 74 | declare function parsePath(pathString: string): WavePoint[]; 75 | declare function interpolateWave( 76 | currentPoints: WavePoint[], 77 | targetPoints: WavePoint[], 78 | progress: number, 79 | vertical?: boolean, 80 | height?: number, 81 | width?: number 82 | ): string; 83 | declare function encodeWaveSeed(pathString: string): string; 84 | declare function decodeWaveSeed(seed: string): string | null; 85 | 86 | // Global declaration for custom element 87 | declare global { 88 | interface HTMLElementTagNameMap { 89 | 'dynamo-wave': DynamoWave; 90 | } 91 | } 92 | 93 | // Component attributes interface 94 | interface DynamoWaveAttributes { 95 | 'data-wave-face'?: WaveDirection; 96 | 'data-wave-points'?: string; 97 | 'data-wave-variance'?: string; 98 | 'data-variance'?: string; 99 | 'data-wave-speed'?: string; 100 | 'data-wave-animate'?: string; 101 | 'data-wave-observe'?: string; 102 | 'data-wave-seed'?: string; 103 | 'data-start-end-zero'?: string; 104 | } 105 | 106 | // Extend HTMLElement interface to include our attributes 107 | declare global { 108 | interface HTMLElementTagNameMap { 109 | 'dynamo-wave': DynamoWave; 110 | } 111 | 112 | namespace JSX { 113 | interface IntrinsicElements { 114 | 'dynamo-wave': Partial; 115 | } 116 | } 117 | } 118 | 119 | export { 120 | DynamoWave, 121 | WaveGenerationOptions, 122 | WavePoint, 123 | WaveDirection, 124 | WaveObserverOptions, 125 | DynamoWaveAttributes, 126 | encodeWaveSeed, 127 | decodeWaveSeed 128 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamowaves 2 | Lightweight, dependency-free SVG wave templates that generate a new path every time they render. Each wave is a standard [custom element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) (``) that swaps itself into the DOM, inherits your styling, and can morph or animate on demand. 3 | 4 | [Documentation + live examples](https://dynamowaves.markzebley.com) 5 | 6 | ## Features 7 | - **Drop-in custom element** – include `` anywhere in your markup; classes, styles, and IDs flow through automatically. 8 | - **Deterministic or generative** – seed waves for reproducible shapes, or let them randomize and re-render via Intersection Observer triggers. 9 | - **Rich data attributes** – configure direction, variance, anchoring, animation speed, observation behavior, and more without writing JS. 10 | - **Runtime controls** – programmatic API (`generateNewWave`, `play`, `pause`) with TypeScript definitions plus a `dynamo-wave-complete` event hook. 11 | - **Animation aware** – honors `prefers-reduced-motion` and pauses observers/loops when the element leaves the DOM. 12 | 13 | ## Installation 14 | ### npm 15 | ```bash 16 | npm install dynamowaves 17 | ``` 18 | 19 | ```js 20 | // Registers the custom element globally 21 | import 'dynamowaves'; 22 | ``` 23 | 24 | ### CDN or direct script 25 | ```html 26 | 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | ### Angular 34 | 1. Add the script to the `angular.json` `scripts` array: 35 | ```json 36 | "scripts": [ 37 | "node_modules/dynamowaves/dist/dynamowaves.js" 38 | ] 39 | ``` 40 | 2. Opt in to custom elements support: 41 | ```ts 42 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 43 | 44 | @NgModule({ 45 | // ... 46 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 47 | }) 48 | export class AppModule {} 49 | ``` 50 | 51 | ## Quick start 52 | ```html 53 | 54 | 55 | 58 | ``` 59 | 60 | ## Data attributes 61 | | Attribute | Default | Purpose | 62 | | --- | --- | --- | 63 | | `data-wave-points` | `6` | Number of anchor points. | 64 | | `data-wave-variance` | `3` | Max point deviation. | 65 | | `data-wave-seed` | _unset_ | Encoded deterministic path. | 66 | | `data-start-end-zero` | _false_ | Anchors endpoints on baseline. | 67 | | `data-wave-face` | `top` | Orientation of the wave. | 68 | | `data-wave-speed` | `7500` | Loop duration. | 69 | | `data-wave-animate` | `false` | Auto-animate. | 70 | | `data-wave-observe` | _unset_ | Regenerate on viewport changes. | 71 | 72 | ## Reusing wave seeds 73 | ```html 74 | 75 | 83 | ``` 84 | 85 | ## JavaScript API 86 | | Method | Description | 87 | | --- | --- | 88 | | `generateNewWave(duration?)` | Morph to a new random path. | 89 | | `play(duration?)` | Start loop. | 90 | | `pause()` | Stop loop. | 91 | 92 | ## Practical ideas 93 | See `www/snippets/practical-application/examples.md` or the docs site. 94 | 95 | ## Accessibility 96 | - Decorative by default. 97 | - Respects reduced-motion. 98 | - Seeds for SSR consistency. 99 | 100 | ## Development 101 | ```bash 102 | git clone https://github.com/mzebley/dynamowaves.git 103 | cd dynamowaves 104 | npm install 105 | npm run build 106 | ``` 107 | 108 | Docs use **mark↓**. 109 | Use: 110 | - `npm run docs:manifest` 111 | - `npm run docs:build` 112 | - `npm run docs:watch` 113 | 114 | ## License 115 | ISC © Mark Zebley 116 | -------------------------------------------------------------------------------- /www/main.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["main.scss"],"names":[],"mappings":"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","file":"main.css"} -------------------------------------------------------------------------------- /www/snippets/practical-application/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: examples 3 | title: Examples 4 | type: docs 5 | group: practical-application 6 | order: 1 7 | groupOrder: 3 8 | groupLabel: "Practical Application" 9 | --- 10 | 11 |

Practical Application

12 | 13 | **Dynamowaves** come out of the box entirely style agnostic. The onus is on the developer to add the necessary attributes to get them to fit their intended platform, but at the same time this provides nearly endless possibilities for customization. 14 | 15 | Make use of **```position:sticky```** and create a neat little header! 16 | 17 | ```html 18 | 19 |
20 |
21 |

I'm the heading!

22 | 23 |
24 |
...
25 |
26 | ``` 27 | 28 |
29 |
30 |

I'm the heading!

31 | 32 |
33 |
Lorem ipsum dolor sit amet, ea sea regione concludaturque. Te eam pericula prodesset 34 | constituto. In forensibus voluptatum nam. Ius ne modus laboramus, quo illud altera mandamus eu. Persius 35 | oportere molestiae vel ut. Ei vel nusquam forensibus eloquentiam. 36 |

37 | Mei in posse error incorrupte. Ex rebum vidisse sea. Per sumo quando mucius cu, no persius signiferumque 38 | eos, 39 | et has deserunt pertinacia. Ea malis everti nostrud sed. 40 |

41 | Id regione prompta denique est, mei at veri essent instructior, mei id congue instructior. Nec ne legere 42 | tritani sadipscing. Oblique propriae theophrastus id quo, vero persequeris vix cu. Qui singulis 43 | pertinacia 44 | ex, 45 | ad agam doctus graecis eos, vis et erat accumsan. 46 |

47 | Cum esse quot essent te, in delicata conceptam cum, dicam iuvaret inimicus mei ad. Est ex cetero commune 48 | eleifend. Vim in aeque constituam, timeam debitis argumentum sed ea. His epicurei evertitur et, ea 49 | sanctus 50 | saperet sed. An harum referrentur nec, te sed errem patrioque, mei et tempor blandit sapientem. Vim te 51 | aeterno 52 | sapientem. 53 |

54 | In eam putant labores accusam. Ne sed evertitur torquatos. Dolor option regione nam ei, summo constituto 55 | usu 56 | at. Postea accusata et has, his te prima porro verterem. 57 |
58 |
59 | 60 | 61 | Use an animated **dynamowave** to add some more pizzazz to transition effects. 62 | 63 |
64 |
65 |
Content 1
66 |
Content 2
67 |
Content 3
68 |
Content 4
69 |
70 | 79 |
80 | 81 | Slap one of these bad boys along the edge of a photo to create an always fresh, **mask-image** effect without having to actually create multiple clip paths or image masks! 82 | 83 | ```html 84 |
85 |
86 | 87 | 90 | 91 |
92 |
...
93 |
94 | ``` 95 | 96 |
97 |
98 | 100 | 101 |
102 |
103 |

104 | Eye-catching headline.

105 |

Further information to draw interest.

106 | Explore This 107 |
108 |
-------------------------------------------------------------------------------- /www/dynamowaves.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Dynamowaves={})}(this,(function(t){"use strict";class e extends HTMLElement{constructor(){super(),this.isAnimating=!1,this.animationFrameId=null,this.elapsedTime=0,this.startTime=null,this.isGeneratingWave=!1,this.currentPath=null,this.targetPath=null,this.pendingTargetPath=null,this.intersectionObserver=null,this.observerOptions=null,this.random=Math.random,this.startEndZero=!1}connectedCallback(){const t=this.id??Math.random().toString(36).substring(7),e=this.getAttribute("data-wave-face")||"top",n=parseInt(this.getAttribute("data-wave-points"),10);this.points=Number.isFinite(n)?Math.max(2,n):6;const r=this.getAttribute("data-wave-variance"),a=this.getAttribute("data-variance"),h=parseFloat(r??a??"");this.variance=Number.isFinite(h)?h:3,this.duration=parseFloat(this.getAttribute("data-wave-speed"))||7500;const o=this.getAttribute("data-wave-seed"),d=s(o),c="string"==typeof o&&""!==o.trim();this.random=c&&!d?function(t){let e=0;const i=String(t);for(let t=0;t>>0;return function(){n+=1831565813;let t=n;return t=Math.imul(t^t>>>15,1|t),t^=t+Math.imul(t^t>>>7,61|t),((t^t>>>14)>>>0)/4294967296}}(o):Math.random;const l=this.getAttribute("data-start-end-zero");this.startEndZero="string"==typeof l&&["","true","1","yes","on"].includes(l.trim().toLowerCase()),this.vertical="left"===e||"right"===e;const u="right"===e,p="bottom"===e;this.width=this.vertical?160:1440,this.height=this.vertical?1440:160,this.currentPath=d||i({width:this.width,height:this.height,points:this.points,variance:this.variance,vertical:this.vertical,random:this.random,startEndZero:this.startEndZero}),this.updateSeedAttribute(this.currentPath),this.targetPath=i({width:this.width,height:this.height,points:this.points,variance:this.variance,vertical:this.vertical,random:this.random,startEndZero:this.startEndZero});const m=[];u&&m.push("scaleX(-1)"),p&&m.push("scaleY(-1)");const g=m.length?`transform:${m.join(" ")};`:"",v="undefined"!=typeof window&&"function"==typeof window.getComputedStyle?window.getComputedStyle(this)?.display:"";this.style.display||"inline"!==v||(this.style.display="block"),this.innerHTML=`\n