Skip to content

Commit 2a682e3

Browse files
committed
add basic dotwave HTML element code and test
1 parent 39eef28 commit 2a682e3

2 files changed

Lines changed: 277 additions & 0 deletions

File tree

dotwave-element-test.html

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="src/dotwave.js"></script>
5+
<script src="src/dotwave-element.js"></script>
6+
</head>
7+
<body>
8+
<!-- Basic usage with default settings -->
9+
<dot-wave style="width: 100%; height: 400px; display: flex; align-items: center; justify-content: center;"
10+
num-dots="200"
11+
dot-stretch="false"
12+
>
13+
<p style="color: white;">This is DotWave with custom settings.</p>
14+
</dot-wave>
15+
16+
<dot-wave
17+
num-dots="200"
18+
dot-color="#FF0000"
19+
background-color="#FFFFFF"
20+
influence-radius="150"
21+
dot-stretch="true"
22+
random-factor="0.3s"
23+
style="width: 800px; height: 500px;">
24+
</dot-wave>
25+
<dot-wave
26+
num-dots="100"
27+
dot-color="#00FF00"
28+
reactive="false"
29+
style="width: 100%; height: 300px;">
30+
</dot-wave>
31+
</body>
32+
</html>

src/dotwave-element.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/**
2+
* DotWave Custom Element
3+
* Wraps the DotWave.js library as a custom HTML element
4+
*/
5+
class DotWaveElement extends HTMLElement {
6+
constructor() {
7+
super();
8+
this.dotwave = null;
9+
this.isInitialized = false;
10+
}
11+
12+
// Define which attributes to observe for changes
13+
static get observedAttributes() {
14+
return [
15+
'num-dots', 'dot-color', 'background-color', 'dot-min-size', 'dot-max-size',
16+
'dot-min-opacity', 'dot-max-opacity', 'influence-radius', 'influence-strength',
17+
'random-factor', 'friction', 'max-speed', 'reactive', 'z-index',
18+
'mouse-speed-decay', 'max-mouse-speed', 'dot-stretch', 'dot-stretch-mult',
19+
'dot-max-stretch', 'rot-smoothing', 'rot-smoothing-intensity'
20+
];
21+
}
22+
23+
// Called when the element is added to the DOM
24+
connectedCallback() {
25+
// Wait for next tick to ensure the element is properly attached
26+
setTimeout(() => {
27+
this.initializeDotWave();
28+
}, 0);
29+
}
30+
31+
// Called when observed attributes change
32+
attributeChangedCallback(name, oldValue, newValue) {
33+
if (oldValue !== newValue && this.isInitialized && this.dotwave) {
34+
this.updateDotWaveOptions();
35+
}
36+
}
37+
38+
// Called when element is removed from DOM
39+
disconnectedCallback() {
40+
if (this.dotwave) {
41+
this.dotwave.destroy();
42+
this.dotwave = null;
43+
this.isInitialized = false;
44+
}
45+
}
46+
47+
// Convert HTML attributes to DotWave options
48+
getOptionsFromAttributes() {
49+
const options = {};
50+
51+
// Map HTML attributes to DotWave options
52+
const attributeMap = {
53+
'num-dots': { prop: 'numDots', type: 'number', default: 400 },
54+
'dot-color': { prop: 'dotColor', type: 'string', default: 'white' },
55+
'background-color': { prop: 'backgroundColor', type: 'string', default: 'black' },
56+
'dot-min-size': { prop: 'dotMinSize', type: 'number', default: 1 },
57+
'dot-maxs-ize': { prop: 'dotMaxSize', type: 'number', default: 3 },
58+
'dot-min-opacity': { prop: 'dotMinOpacity', type: 'number', default: 0.5 },
59+
'dot-max-opacity': { prop: 'dotMaxOpacity', type: 'number', default: 1 },
60+
'influence-radius': { prop: 'influenceRadius', type: 'number', default: 100 },
61+
'influence-strength': { prop: 'influenceStrength', type: 'number', default: 0.5 },
62+
'random-factor': { prop: 'randomFactor', type: 'number', default: 0.05 },
63+
'friction': { prop: 'friction', type: 'number', default: 0.97 },
64+
'max-speed': { prop: 'maxSpeed', type: 'number', default: 3 },
65+
'reactive': { prop: 'reactive', type: 'boolean', default: true },
66+
'z-index': { prop: 'zIndex', type: 'number', default: -1 },
67+
'mouse-speed-decay': { prop: 'mouseSpeedDecay', type: 'number', default: 0.85 },
68+
'max-mouse-speed': { prop: 'maxMouseSpeed', type: 'number', default: 15 },
69+
'dot-stretch': { prop: 'dotStretch', type: 'boolean', default: true },
70+
'dot-stretch-mult': { prop: 'dotStretchMult', type: 'number', default: 10 },
71+
'dot-max-stretch': { prop: 'dotMaxStretch', type: 'number', default: 20 },
72+
'rot-smoothing': { prop: 'rotSmoothing', type: 'boolean', default: false },
73+
'rot-smoothing-intensity': { prop: 'rotSmoothingIntensity', type: 'number', default: 150 }
74+
};
75+
76+
// Process each attribute
77+
for (const [htmlAttr, config] of Object.entries(attributeMap)) {
78+
if (this.hasAttribute(htmlAttr)) {
79+
const value = this.getAttribute(htmlAttr);
80+
81+
// Convert string values to appropriate types
82+
if (config.type === 'boolean') {
83+
// Boolean attributes: presence means true, "false" means false
84+
options[config.prop] = value !== 'false' && value !== null;
85+
} else if (config.type === 'number') {
86+
const numValue = parseFloat(value);
87+
options[config.prop] = isNaN(numValue) ? config.default : numValue;
88+
} else {
89+
// String values
90+
options[config.prop] = value || config.default;
91+
}
92+
}
93+
}
94+
95+
// Set the container to this element
96+
options.container = this;
97+
98+
return options;
99+
}
100+
101+
initializeDotWave() {
102+
if (this.isInitialized) return;
103+
104+
// Ensure DotWave is available
105+
if (typeof DotWave === 'undefined') {
106+
console.error('DotWave library is not loaded. Please include dotwave.js before the custom element.');
107+
return;
108+
}
109+
110+
const options = this.getOptionsFromAttributes();
111+
112+
// Set up element styling
113+
this.setupElementStyles();
114+
115+
// Create DotWave instance
116+
try {
117+
this.dotwave = new DotWave(options);
118+
this.isInitialized = true;
119+
} catch (error) {
120+
console.error('Error initializing DotWave:', error);
121+
}
122+
}
123+
124+
setupElementStyles() {
125+
// Ensure the element has proper dimensions and positioning
126+
const computedStyle = window.getComputedStyle(this);
127+
128+
// Set display to block if not specified
129+
if (computedStyle.display === 'inline') {
130+
this.style.display = 'block';
131+
}
132+
133+
// Set position to relative if static
134+
if (computedStyle.position === 'static') {
135+
this.style.position = 'relative';
136+
}
137+
138+
// Set default dimensions if not specified
139+
if (!this.style.width && computedStyle.width === '0px') {
140+
this.style.width = '100%';
141+
}
142+
143+
if (!this.style.height && computedStyle.height === '0px') {
144+
this.style.height = '400px';
145+
}
146+
147+
// Ensure overflow is hidden for clean appearance
148+
if (!this.style.overflow) {
149+
this.style.overflow = 'hidden';
150+
}
151+
}
152+
153+
updateDotWaveOptions() {
154+
if (!this.dotwave) return;
155+
156+
const options = this.getOptionsFromAttributes();
157+
// Remove container from update options since it shouldn't change
158+
delete options.container;
159+
160+
this.dotwave.updateOptions(options);
161+
}
162+
163+
// Property getters and setters for JavaScript API compatibility
164+
get numDots() {
165+
return this.hasAttribute('num-dots') ? parseInt(this.getAttribute('num-dots')) : 400;
166+
}
167+
168+
set numDots(value) {
169+
if (value !== null && value !== undefined) {
170+
this.setAttribute('num-dots', value.toString());
171+
} else {
172+
this.removeAttribute('num-dots');
173+
}
174+
}
175+
176+
get dotColor() {
177+
return this.getAttribute('dot-color') || 'white';
178+
}
179+
180+
set dotColor(value) {
181+
if (value) {
182+
this.setAttribute('dot-color', value);
183+
} else {
184+
this.removeAttribute('dot-color');
185+
}
186+
}
187+
188+
get backgroundColor() {
189+
return this.getAttribute('background-color') || 'black';
190+
}
191+
192+
set backgroundColor(value) {
193+
if (value) {
194+
this.setAttribute('background-color', value);
195+
} else {
196+
this.removeAttribute('background-color');
197+
}
198+
}
199+
200+
get reactive() {
201+
return this.hasAttribute('reactive') ? this.getAttribute('reactive') !== 'false' : true;
202+
}
203+
204+
set reactive(value) {
205+
if (value) {
206+
this.setAttribute('reactive', 'true');
207+
} else {
208+
this.setAttribute('reactive', 'false');
209+
}
210+
}
211+
212+
// Methods to control the DotWave instance
213+
pause() {
214+
if (this.dotwave) {
215+
this.dotwave.pause();
216+
}
217+
}
218+
219+
resume() {
220+
if (this.dotwave) {
221+
this.dotwave.resume();
222+
}
223+
}
224+
225+
destroy() {
226+
if (this.dotwave) {
227+
this.dotwave.destroy();
228+
this.dotwave = null;
229+
this.isInitialized = false;
230+
}
231+
}
232+
233+
// Get access to the underlying DotWave instance
234+
getDotWaveInstance() {
235+
return this.dotwave;
236+
}
237+
}
238+
239+
// Register the custom element
240+
customElements.define('dot-wave', DotWaveElement);
241+
242+
// Export for module systems
243+
if (typeof module !== 'undefined' && module.exports) {
244+
module.exports = DotWaveElement;
245+
}

0 commit comments

Comments
 (0)