From 93436290f2e670eb5b5fd0f2aaaed2b52367fc63 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 16:28:16 +0000 Subject: [PATCH 1/2] Optimize dot rendering rotation performance Replaced expensive canvas state modifications (save, translate, rotate, restore) with native ellipse rotation parameter. This change significantly reduces the number of canvas API calls per drawn dot in dot stretching mode. Co-authored-by: jsem-nerad <88319121+jsem-nerad@users.noreply.github.com> --- .gitignore | 1 + src/dotwave.js | 14 ++------------ 2 files changed, 3 insertions(+), 12 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/src/dotwave.js b/src/dotwave.js index def1dea..e65f11e 100644 --- a/src/dotwave.js +++ b/src/dotwave.js @@ -394,21 +394,11 @@ const radiusX = dot.radius + stretchAmount; const radiusY = dot.radius; - // Save context state - this.ctx.save(); - - // Translate to dot position and rotate - this.ctx.translate(dot.x, dot.y); - this.ctx.rotate(dot.currentAngle); - - // Draw stretched ellipse + // Draw stretched ellipse with built-in rotation this.ctx.beginPath(); - this.ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, Math.PI * 2); + this.ctx.ellipse(dot.x, dot.y, radiusX, radiusY, dot.currentAngle, 0, Math.PI * 2); this.ctx.fillStyle = fillStyle; this.ctx.fill(); - - // Restore context state - this.ctx.restore(); }; /** From 2ba6d18b2807aaf41eb9f509f8d23c897ea3f4f5 Mon Sep 17 00:00:00 2001 From: jsem-nerad <88319121+jsem-nerad@users.noreply.github.com> Date: Sat, 23 May 2026 16:28:34 +0000 Subject: [PATCH 2/2] Auto-minify JS files in /src directory --- src/dotwave.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotwave.min.js b/src/dotwave.min.js index efa7ab5..d5aec4d 100644 --- a/src/dotwave.min.js +++ b/src/dotwave.min.js @@ -1 +1 @@ -!function(t){"use strict";function e(t){this.defaults={container:"body",numDots:400,dotColor:"white",backgroundColor:"black",dotMinSize:1,dotMaxSize:3,dotMinOpacity:.5,dotMaxOpacity:1,influenceRadius:100,influenceStrength:.5,randomFactor:.05,friction:.97,maxSpeed:3,reactive:!0,zIndex:-1,mouseSpeedDecay:.85,maxMouseSpeed:15,dotStretch:!0,dotStretchMult:10,dotMaxStretch:20,rotSmoothing:!1,rotSmoothingIntensity:150},this.options=this._mergeOptions(this.defaults,t||{}),this.canvas=null,this.ctx=null,this.container=null,this.dots=[],this.width=0,this.height=0,this.mouseX=0,this.mouseY=0,this.prevMouseX=0,this.prevMouseY=0,this.mouseSpeedX=0,this.mouseSpeedY=0,this.animationFrame=null,this.resizeTimeout=null,this.lastFrameTime=0,this.isMouseOver=!1,this.init()}e.prototype.init=function(){this.container=this._getContainer(this.options.container),this.container?(this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.canvas.style.position="absolute",this.canvas.style.top="0",this.canvas.style.left="0",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.zIndex=this.options.zIndex,this.canvas.style.pointerEvents="none",this._setupContainer(),this.container.appendChild(this.canvas),this._updateCanvasSize(),this._createDots(),this._addEventListeners(),this.lastFrameTime=performance.now(),this._animate()):console.error("DotWave: Container element not found")},e.prototype._setupContainer=function(){"static"===window.getComputedStyle(this.container).position&&(this.container.style.position="relative"),this.options.backgroundColor&&this.container===document.body&&(document.body.style.backgroundColor=this.options.backgroundColor)},e.prototype._getContainer=function(t){return"string"==typeof t?document.querySelector(t):t instanceof Element?t:t===document.body?document.body:null},e.prototype._mergeOptions=function(t,e){const i={};for(const s in t)i[s]=void 0!==e[s]?e[s]:t[s];return i},e.prototype._updateCanvasSize=function(){const t=this.container.getBoundingClientRect(),e=t.width,i=t.height,s=this.width||e,o=this.height||i;this.width=e,this.height=i;const n=window.devicePixelRatio||1;if(this.canvas.width=this.width*n,this.canvas.height=this.height*n,this.ctx.scale(n,n),this.dots&&this.dots.length>0&&(s!==e||o!==i)){const t=e/s,n=i/o;for(let s=0;se+50&&(o.x=e+50),o.y<-50&&(o.y=-50),o.y>i+50&&(o.y=i+50)}}this.mouseX=this.width/2,this.mouseY=this.height/2,this.prevMouseX=this.mouseX,this.prevMouseY=this.mouseY},e.prototype._createDots=function(){this.dots=[];for(let t=0;tthis.options.maxMouseSpeed){const t=this.options.maxMouseSpeed/o;this.mouseSpeedX=i*t,this.mouseSpeedY=s*t}else this.mouseSpeedX=i,this.mouseSpeedY=s},e.prototype._handleMouseEnter=function(){this.isMouseOver=!0},e.prototype._handleMouseLeave=function(){this.isMouseOver=!1,this.mouseSpeedX*=.5,this.mouseSpeedY*=.5},e.prototype._handleResize=function(){clearTimeout(this.resizeTimeout),this.resizeTimeout=setTimeout((()=>{this._updateCanvasSize()}),250)},e.prototype._lerpAngle=function(t,e,i){let s=e-t;for(;s>Math.PI;)s-=2*Math.PI;for(;s<-Math.PI;)s+=2*Math.PI;let o=t+s*i;for(;o<0;)o+=2*Math.PI;for(;o>=2*Math.PI;)o-=2*Math.PI;return o},e.prototype._drawDot=function(t,e){const i=this._getRGBA(this.options.dotColor,t.alpha);if(!this.options.dotStretch)return this.ctx.beginPath(),this.ctx.arc(t.x,t.y,t.radius,0,2*Math.PI),this.ctx.fillStyle=i,void this.ctx.fill();const s=t.vx*t.vx,o=t.vy*t.vy,n=Math.sqrt(s+o);let h=Math.min(n/this.options.maxSpeed,1)*this.options.dotStretchMult;if(this.options.dotMaxStretch&&(h=Math.min(h,this.options.dotMaxStretch)),h<.01)return this.ctx.beginPath(),this.ctx.arc(t.x,t.y,t.radius,0,2*Math.PI),this.ctx.fillStyle=i,void this.ctx.fill();const a=Math.atan2(t.vy,t.vx);if(this.options.rotSmoothing){t.targetAngle=a;const i=this.options.rotSmoothingIntensity<=0?1:Math.min(e/this.options.rotSmoothingIntensity,1);t.currentAngle=this._lerpAngle(t.currentAngle,t.targetAngle,i)}else t.currentAngle=a;const r=t.radius+h,c=t.radius;this.ctx.save(),this.ctx.translate(t.x,t.y),this.ctx.rotate(t.currentAngle),this.ctx.beginPath(),this.ctx.ellipse(0,0,r,c,0,0,2*Math.PI),this.ctx.fillStyle=i,this.ctx.fill(),this.ctx.restore()},e.prototype._animate=function(){this.animationFrame=requestAnimationFrame(this._animate.bind(this));const t=performance.now(),e=Math.min(t-this.lastFrameTime,32),i=e/10;this.lastFrameTime=t,this.options.reactive&&(this.mouseSpeedX*=Math.pow(this.options.mouseSpeedDecay,i),this.mouseSpeedY*=Math.pow(this.options.mouseSpeedDecay,i)),this.ctx.clearRect(0,0,this.width,this.height),"transparent"!==this.options.backgroundColor&&(this.ctx.fillStyle=this.options.backgroundColor,this.ctx.fillRect(0,0,this.width,this.height));const s=this.options.randomFactor,o=Math.pow(this.options.friction,i),n=this.options.maxSpeed,h=n*n;let a,r,c;this.options.reactive&&(a=this.options.influenceRadius,r=a*a,c=this.options.influenceStrength);for(let t=0;th){const t=n/Math.sqrt(u);d.vx*=t,d.vy*=t}d.x+=d.vx*d.speedMultiplier*i,d.y+=d.vy*d.speedMultiplier*i,d.x<-50&&(d.x=this.width+50),d.x>this.width+50&&(d.x=-50),d.y<-50&&(d.y=this.height+50),d.y>this.height+50&&(d.y=-50),this._drawDot(d,e)}},e.prototype._getRGBA=function(t,e){if(t.startsWith("rgba"))return t;if(t.startsWith("rgb"))return t.replace("rgb","rgba").replace(")",`, ${e})`);if(t.startsWith("#")){const i=t.substring(1);return`rgba(${parseInt(3===i.length?i.charAt(0)+i.charAt(0):i.substring(0,2),16)}, ${parseInt(3===i.length?i.charAt(1)+i.charAt(1):i.substring(2,4),16)}, ${parseInt(3===i.length?i.charAt(2)+i.charAt(2):i.substring(4,6),16)}, ${e})`}return{white:"rgba(255, 255, 255, "+e+")",black:"rgba(0, 0, 0, "+e+")",red:"rgba(255, 0, 0, "+e+")",green:"rgba(0, 128, 0, "+e+")",blue:"rgba(0, 0, 255, "+e+")",yellow:"rgba(255, 255, 0, "+e+")",orange:"rgba(255, 165, 0, "+e+")",purple:"rgba(128, 0, 128, "+e+")",pink:"rgba(255, 192, 203, "+e+")",brown:"rgba(165, 42, 42, "+e+")",gray:"rgba(128, 128, 128, "+e+")",grey:"rgba(128, 128, 128, "+e+")",cyan:"rgba(0, 255, 255, "+e+")",magenta:"rgba(255, 0, 255, "+e+")",lime:"rgba(0, 255, 0, "+e+")",maroon:"rgba(128, 0, 0, "+e+")",navy:"rgba(0, 0, 128, "+e+")",olive:"rgba(128, 128, 0, "+e+")",teal:"rgba(0, 128, 128, "+e+")",silver:"rgba(192, 192, 192, "+e+")",gold:"rgba(255, 215, 0, "+e+")",indigo:"rgba(75, 0, 130, "+e+")",violet:"rgba(238, 130, 238, "+e+")",turquoise:"rgba(64, 224, 208, "+e+")"}[t.toLowerCase()]||`rgba(255, 255, 255, ${e})`},e.prototype.destroy=function(){this.animationFrame&&cancelAnimationFrame(this.animationFrame),this.container.removeEventListener("mousemove",this._handleMouseMove),this.container.removeEventListener("mouseenter",this._handleMouseEnter),this.container.removeEventListener("mouseleave",this._handleMouseLeave),window.removeEventListener("resize",this._handleResize),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null,this.dots=[]},e.prototype.updateOptions=function(t){this.options=this._mergeOptions(this.options,t||{}),void 0===t.numDots&&void 0===t.dotStretch&&void 0===t.rotSmoothing||this._createDots()},e.prototype.pause=function(){this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=null)},e.prototype.resume=function(){this.animationFrame||(this.lastFrameTime=performance.now(),this._animate())},t.DotWave=e}("undefined"!=typeof window?window:this); +!function(t){"use strict";function e(t){this.defaults={container:"body",numDots:400,dotColor:"white",backgroundColor:"black",dotMinSize:1,dotMaxSize:3,dotMinOpacity:.5,dotMaxOpacity:1,influenceRadius:100,influenceStrength:.5,randomFactor:.05,friction:.97,maxSpeed:3,reactive:!0,zIndex:-1,mouseSpeedDecay:.85,maxMouseSpeed:15,dotStretch:!0,dotStretchMult:10,dotMaxStretch:20,rotSmoothing:!1,rotSmoothingIntensity:150},this.options=this._mergeOptions(this.defaults,t||{}),this.canvas=null,this.ctx=null,this.container=null,this.dots=[],this.width=0,this.height=0,this.mouseX=0,this.mouseY=0,this.prevMouseX=0,this.prevMouseY=0,this.mouseSpeedX=0,this.mouseSpeedY=0,this.animationFrame=null,this.resizeTimeout=null,this.lastFrameTime=0,this.isMouseOver=!1,this.init()}e.prototype.init=function(){this.container=this._getContainer(this.options.container),this.container?(this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.canvas.style.position="absolute",this.canvas.style.top="0",this.canvas.style.left="0",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.zIndex=this.options.zIndex,this.canvas.style.pointerEvents="none",this._setupContainer(),this.container.appendChild(this.canvas),this._updateCanvasSize(),this._createDots(),this._addEventListeners(),this.lastFrameTime=performance.now(),this._animate()):console.error("DotWave: Container element not found")},e.prototype._setupContainer=function(){"static"===window.getComputedStyle(this.container).position&&(this.container.style.position="relative"),this.options.backgroundColor&&this.container===document.body&&(document.body.style.backgroundColor=this.options.backgroundColor)},e.prototype._getContainer=function(t){return"string"==typeof t?document.querySelector(t):t instanceof Element?t:t===document.body?document.body:null},e.prototype._mergeOptions=function(t,e){const i={};for(const s in t)i[s]=void 0!==e[s]?e[s]:t[s];return i},e.prototype._updateCanvasSize=function(){const t=this.container.getBoundingClientRect(),e=t.width,i=t.height,s=this.width||e,o=this.height||i;this.width=e,this.height=i;const n=window.devicePixelRatio||1;if(this.canvas.width=this.width*n,this.canvas.height=this.height*n,this.ctx.scale(n,n),this.dots&&this.dots.length>0&&(s!==e||o!==i)){const t=e/s,n=i/o;for(let s=0;se+50&&(o.x=e+50),o.y<-50&&(o.y=-50),o.y>i+50&&(o.y=i+50)}}this.mouseX=this.width/2,this.mouseY=this.height/2,this.prevMouseX=this.mouseX,this.prevMouseY=this.mouseY},e.prototype._createDots=function(){this.dots=[];for(let t=0;tthis.options.maxMouseSpeed){const t=this.options.maxMouseSpeed/o;this.mouseSpeedX=i*t,this.mouseSpeedY=s*t}else this.mouseSpeedX=i,this.mouseSpeedY=s},e.prototype._handleMouseEnter=function(){this.isMouseOver=!0},e.prototype._handleMouseLeave=function(){this.isMouseOver=!1,this.mouseSpeedX*=.5,this.mouseSpeedY*=.5},e.prototype._handleResize=function(){clearTimeout(this.resizeTimeout),this.resizeTimeout=setTimeout((()=>{this._updateCanvasSize()}),250)},e.prototype._lerpAngle=function(t,e,i){let s=e-t;for(;s>Math.PI;)s-=2*Math.PI;for(;s<-Math.PI;)s+=2*Math.PI;let o=t+s*i;for(;o<0;)o+=2*Math.PI;for(;o>=2*Math.PI;)o-=2*Math.PI;return o},e.prototype._drawDot=function(t,e){const i=this._getRGBA(this.options.dotColor,t.alpha);if(!this.options.dotStretch)return this.ctx.beginPath(),this.ctx.arc(t.x,t.y,t.radius,0,2*Math.PI),this.ctx.fillStyle=i,void this.ctx.fill();const s=t.vx*t.vx,o=t.vy*t.vy,n=Math.sqrt(s+o);let h=Math.min(n/this.options.maxSpeed,1)*this.options.dotStretchMult;if(this.options.dotMaxStretch&&(h=Math.min(h,this.options.dotMaxStretch)),h<.01)return this.ctx.beginPath(),this.ctx.arc(t.x,t.y,t.radius,0,2*Math.PI),this.ctx.fillStyle=i,void this.ctx.fill();const a=Math.atan2(t.vy,t.vx);if(this.options.rotSmoothing){t.targetAngle=a;const i=this.options.rotSmoothingIntensity<=0?1:Math.min(e/this.options.rotSmoothingIntensity,1);t.currentAngle=this._lerpAngle(t.currentAngle,t.targetAngle,i)}else t.currentAngle=a;const r=t.radius+h,c=t.radius;this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,r,c,t.currentAngle,0,2*Math.PI),this.ctx.fillStyle=i,this.ctx.fill()},e.prototype._animate=function(){this.animationFrame=requestAnimationFrame(this._animate.bind(this));const t=performance.now(),e=Math.min(t-this.lastFrameTime,32),i=e/10;this.lastFrameTime=t,this.options.reactive&&(this.mouseSpeedX*=Math.pow(this.options.mouseSpeedDecay,i),this.mouseSpeedY*=Math.pow(this.options.mouseSpeedDecay,i)),this.ctx.clearRect(0,0,this.width,this.height),"transparent"!==this.options.backgroundColor&&(this.ctx.fillStyle=this.options.backgroundColor,this.ctx.fillRect(0,0,this.width,this.height));const s=this.options.randomFactor,o=Math.pow(this.options.friction,i),n=this.options.maxSpeed,h=n*n;let a,r,c;this.options.reactive&&(a=this.options.influenceRadius,r=a*a,c=this.options.influenceStrength);for(let t=0;th){const t=n/Math.sqrt(u);d.vx*=t,d.vy*=t}d.x+=d.vx*d.speedMultiplier*i,d.y+=d.vy*d.speedMultiplier*i,d.x<-50&&(d.x=this.width+50),d.x>this.width+50&&(d.x=-50),d.y<-50&&(d.y=this.height+50),d.y>this.height+50&&(d.y=-50),this._drawDot(d,e)}},e.prototype._getRGBA=function(t,e){if(t.startsWith("rgba"))return t;if(t.startsWith("rgb"))return t.replace("rgb","rgba").replace(")",`, ${e})`);if(t.startsWith("#")){const i=t.substring(1);return`rgba(${parseInt(3===i.length?i.charAt(0)+i.charAt(0):i.substring(0,2),16)}, ${parseInt(3===i.length?i.charAt(1)+i.charAt(1):i.substring(2,4),16)}, ${parseInt(3===i.length?i.charAt(2)+i.charAt(2):i.substring(4,6),16)}, ${e})`}return{white:"rgba(255, 255, 255, "+e+")",black:"rgba(0, 0, 0, "+e+")",red:"rgba(255, 0, 0, "+e+")",green:"rgba(0, 128, 0, "+e+")",blue:"rgba(0, 0, 255, "+e+")",yellow:"rgba(255, 255, 0, "+e+")",orange:"rgba(255, 165, 0, "+e+")",purple:"rgba(128, 0, 128, "+e+")",pink:"rgba(255, 192, 203, "+e+")",brown:"rgba(165, 42, 42, "+e+")",gray:"rgba(128, 128, 128, "+e+")",grey:"rgba(128, 128, 128, "+e+")",cyan:"rgba(0, 255, 255, "+e+")",magenta:"rgba(255, 0, 255, "+e+")",lime:"rgba(0, 255, 0, "+e+")",maroon:"rgba(128, 0, 0, "+e+")",navy:"rgba(0, 0, 128, "+e+")",olive:"rgba(128, 128, 0, "+e+")",teal:"rgba(0, 128, 128, "+e+")",silver:"rgba(192, 192, 192, "+e+")",gold:"rgba(255, 215, 0, "+e+")",indigo:"rgba(75, 0, 130, "+e+")",violet:"rgba(238, 130, 238, "+e+")",turquoise:"rgba(64, 224, 208, "+e+")"}[t.toLowerCase()]||`rgba(255, 255, 255, ${e})`},e.prototype.destroy=function(){this.animationFrame&&cancelAnimationFrame(this.animationFrame),this.container.removeEventListener("mousemove",this._handleMouseMove),this.container.removeEventListener("mouseenter",this._handleMouseEnter),this.container.removeEventListener("mouseleave",this._handleMouseLeave),window.removeEventListener("resize",this._handleResize),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null,this.dots=[]},e.prototype.updateOptions=function(t){this.options=this._mergeOptions(this.options,t||{}),void 0===t.numDots&&void 0===t.dotStretch&&void 0===t.rotSmoothing||this._createDots()},e.prototype.pause=function(){this.animationFrame&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=null)},e.prototype.resume=function(){this.animationFrame||(this.lastFrameTime=performance.now(),this._animate())},t.DotWave=e}("undefined"!=typeof window?window:this);