当前位置: 首页 > news >正文

Canvas/SVG 冷门用法:实现动态背景与简易数据可视化

Canvas/SVG 冷门用法:实现动态背景与简易数据可视化

文章目录

  • Canvas/SVG 冷门用法:实现动态背景与简易数据可视化
    • 1. Canvas冷门用法实战
      • 1.1 动态粒子背景系统
      • 1.2 音频可视化效果
      • 1.3 图像处理与滤镜
      • 1.4 物理引擎模拟
    • 2. SVG冷门用法实战
      • 2.1 动态图表生成
      • 2.2 路径动画
    • 3. 动态背景实现
      • 3.1 波浪背景
      • 3.2 几何图形动画
    • 4. 简易数据可视化
      • 4.1 实时图表
      • 4.2 进度指示器
    • 5. 性能优化技巧
      • 5.1 Canvas性能优化
      • 5.2 SVG性能优化
    • 6. 实战项目:动态背景库
      • 6.1 完整的动态背景库
    • 总结

Canvas和SVG作为前端图形处理的两大核心技术,除了常见的图表绘制和简单动画外,还隐藏着许多鲜为人知的高级用法。本文将带你探索这些冷门技巧,打造炫酷的动态背景和实用的数据可视化效果。

1. Canvas冷门用法实战

1.1 动态粒子背景系统

粒子系统是创建动态背景的经典方法,通过Canvas可以实现高性能的粒子效果:

class ParticleSystem {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.particles = [];this.mouse = { x: 0, y: 0 };this.connectionDistance = 100;this.init();}init() {this.resize();this.createParticles();this.bindEvents();this.animate();}resize() {this.canvas.width = window.innerWidth;this.canvas.height = window.innerHeight;}createParticles() {const particleCount = Math.floor((this.canvas.width * this.canvas.height) / 15000);for (let i = 0; i < particleCount; i++) {this.particles.push({x: Math.random() * this.canvas.width,y: Math.random() * this.canvas.height,vx: (Math.random() - 0.5) * 0.5,vy: (Math.random() - 0.5) * 0.5,radius: Math.random() * 2 + 1,opacity: Math.random() * 0.5 + 0.5});}}bindEvents() {window.addEventListener('resize', () => this.resize());this.canvas.addEventListener('mousemove', (e) => {this.mouse.x = e.clientX;this.mouse.y = e.clientY;});}updateParticles() {this.particles.forEach(particle => {// 基础移动particle.x += particle.vx;particle.y += particle.vy;// 边界检测if (particle.x < 0 || particle.x > this.canvas.width) particle.vx *= -1;if (particle.y < 0 || particle.y > this.canvas.height) particle.vy *= -1;// 鼠标交互const dx = this.mouse.x - particle.x;const dy = this.mouse.y - particle.y;const distance = Math.sqrt(dx * dx + dy * dy);if (distance < 100) {const force = (100 - distance) / 100;particle.vx += (dx / distance) * force * 0.02;particle.vy += (dy / distance) * force * 0.02;}// 速度限制const speed = Math.sqrt(particle.vx * particle.vx + particle.vy * particle.vy);if (speed > 2) {particle.vx = (particle.vx / speed) * 2;particle.vy = (particle.vy / speed) * 2;}});}drawConnections() {this.ctx.strokeStyle = 'rgba(100, 200, 255, 0.1)';this.ctx.lineWidth = 1;for (let i = 0; i < this.particles.length; i++) {for (let j = i + 1; j < this.particles.length; j++) {const dx = this.particles[i].x - this.particles[j].x;const dy = this.particles[i].y - this.particles[j].y;const distance = Math.sqrt(dx * dx + dy * dy);if (distance < this.connectionDistance) {this.ctx.globalAlpha = (1 - distance / this.connectionDistance) * 0.5;this.ctx.beginPath();this.ctx.moveTo(this.particles[i].x, this.particles[i].y);this.ctx.lineTo(this.particles[j].x, this.particles[j].y);this.ctx.stroke();}}}}drawParticles() {this.particles.forEach(particle => {this.ctx.globalAlpha = particle.opacity;this.ctx.fillStyle = 'rgba(100, 200, 255, 1)';this.ctx.beginPath();this.ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);this.ctx.fill();});}animate() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.updateParticles();this.drawConnections();this.drawParticles();requestAnimationFrame(() => this.animate());}
}// 使用示例
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
new ParticleSystem(canvas);

1.2 音频可视化效果

结合Web Audio API,Canvas可以实现炫酷的音频可视化:

class AudioVisualizer {constructor(canvas, audioElement) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.audio = audioElement;this.audioContext = new (window.AudioContext || window.webkitAudioContext)();this.analyser = this.audioContext.createAnalyser();this.source = null;this.dataArray = null;this.animationId = null;this.setupAnalyser();this.connectAudio();}setupAnalyser() {this.analyser.fftSize = 256;this.analyser.smoothingTimeConstant = 0.8;const bufferLength = this.analyser.frequencyBinCount;this.dataArray = new Uint8Array(bufferLength);}connectAudio() {if (this.audio.src) {this.source = this.audioContext.createMediaElementSource(this.audio);this.source.connect(this.analyser);this.analyser.connect(this.audioContext.destination);}}drawCircularVisualizer() {const centerX = this.canvas.width / 2;const centerY = this.canvas.height / 2;const radius = Math.min(centerX, centerY) - 50;this.analyser.getByteFrequencyData(this.dataArray);this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);// 绘制圆形频谱for (let i = 0; i < this.dataArray.length; i++) {const angle = (i / this.dataArray.length) * Math.PI * 2;const barHeight = (this.dataArray[i] / 255) * radius;const x1 = centerX + Math.cos(angle) * radius;const y1 = centerY + Math.sin(angle) * radius;const x2 = centerX + Math.cos(angle) * (radius + barHeight);const y2 = centerY + Math.sin(angle) * (radius + barHeight);const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);gradient.addColorStop(0, `hsl(${(i / this.dataArray.length) * 360}, 100%, 50%)`);gradient.addColorStop(1, `hsl(${(i / this.dataArray.length) * 360}, 100%, 70%)`);this.ctx.strokeStyle = gradient;this.ctx.lineWidth = 3;this.ctx.beginPath();this.ctx.moveTo(x1, y1);this.ctx.lineTo(x2, y2);this.ctx.stroke();}// 绘制中心圆this.ctx.beginPath();this.ctx.arc(centerX, centerY, radius * 0.3, 0, Math.PI * 2);this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';this.ctx.fill();this.animationId = requestAnimationFrame(() => this.drawCircularVisualizer());}drawWaveform() {this.analyser.getByteTimeDomainData(this.dataArray);this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.lineWidth = 2;this.ctx.strokeStyle = 'rgb(0, 255, 0)';this.ctx.beginPath();const sliceWidth = this.canvas.width / this.dataArray.length;let x = 0;for (let i = 0; i < this.dataArray.length; i++) {const v = this.dataArray[i] / 128.0;const y = v * this.canvas.height / 2;if (i === 0) {this.ctx.moveTo(x, y);} else {this.ctx.lineTo(x, y);}x += sliceWidth;}this.ctx.stroke();this.animationId = requestAnimationFrame(() => this.drawWaveform());}start(visualizationType = 'circular') {if (this.animationId) {cancelAnimationFrame(this.animationId);}switch (visualizationType) {case 'circular':this.drawCircularVisualizer();break;case 'waveform':this.drawWaveform();break;default:this.drawCircularVisualizer();}}stop() {if (this.animationId) {cancelAnimationFrame(this.animationId);this.animationId = null;}}
}// 使用示例
const audio = document.querySelector('audio');
const canvas = document.getElementById('audio-visualizer');
const visualizer = new AudioVisualizer(canvas, audio);audio.addEventListener('play', () => visualizer.start('circular'));
audio.addEventListener('pause', () => visualizer.stop());

1.3 图像处理与滤镜

Canvas可以实现实时的图像处理和滤镜效果:

class ImageProcessor {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.originalImageData = null;}loadImage(src) {return new Promise((resolve, reject) => {const img = new Image();img.onload = () => {this.canvas.width = img.width;this.canvas.height = img.height;this.ctx.drawImage(img, 0, 0);this.originalImageData = this.ctx.getImageData(0, 0, img.width, img.height);resolve(img);};img.onerror = reject;img.src = src;});}applyGrayscale() {const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);const data = imageData.data;for (let i = 0; i < data.length; i += 4) {const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;data[i] = gray;data[i + 1] = gray;data[i + 2] = gray;}this.ctx.putImageData(imageData, 0, 0);}applySepia() {const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);const data = imageData.data;for (let i = 0; i < data.length; i += 4) {const r = data[i];const g = data[i + 1];const b = data[i + 2];data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));data[i + 1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));data[i + 2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));}this.ctx.putImageData(imageData, 0, 0);}applyBlur(radius = 5) {const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);const data = imageData.data;const width = this.canvas.width;const height = this.canvas.height;const output = new Uint8ClampedArray(data);const kernel = this.createGaussianKernel(radius);const kernelSize = kernel.length;const halfSize = Math.floor(kernelSize / 2);for (let y = 0; y < height; y++) {for (let x = 0; x < width; x++) {let r = 0, g = 0, b = 0, a = 0;for (let ky = -halfSize; ky <= halfSize; ky++) {for (let kx = -halfSize; kx <= halfSize; kx++) {const px = Math.min(width - 1, Math.max(0, x + kx));const py = Math.min(height - 1, Math.max(0, y + ky));const weight = kernel[ky + halfSize][kx + halfSize];const idx = (py * width + px) * 4;r += data[idx] * weight;g += data[idx + 1] * weight;b += data[idx + 2] * weight;a += data[idx + 3] * weight;}}const outputIdx = (y * width + x) * 4;output[outputIdx] = r;output[outputIdx + 1] = g;output[outputIdx + 2] = b;output[outputIdx + 3] = a;}}const outputImageData = new ImageData(output, width, height);this.ctx.putImageData(outputImageData, 0, 0);}createGaussianKernel(radius) {const size = radius * 2 + 1;const kernel = [];const sigma = radius / 3;const sigma22 = 2 * sigma * sigma;const sqrtPiSigma = Math.sqrt(2 * Math.PI) * sigma;let sum = 0;for (let y = -radius; y <= radius; y++) {const row = [];for (let x = -radius; x <= radius; x++) {const distance = x * x + y * y;const weight = Math.exp(-distance / sigma22) / sqrtPiSigma;row.push(weight);sum += weight;}kernel.push(row);}// 归一化for (let y = 0; y < size; y++) {for (let x = 0; x < size; x++) {kernel[y][x] /= sum;}}return kernel;}reset() {if (this.originalImageData) {this.ctx.putImageData(this.originalImageData, 0, 0);}}
}// 使用示例
const processor = new ImageProcessor(document.getElementById('image-canvas'));
processor.loadImage('path/to/image.jpg').then(() => {processor.applyGrayscale();// processor.applySepia();// processor.applyBlur(10);
});

1.4 物理引擎模拟

Canvas可以实现简单的物理引擎模拟:

class PhysicsEngine {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.bodies = [];this.gravity = { x: 0, y: 0.5 };this.damping = 0.99;this.bounce = 0.8;this.animate();}addBody(body) {this.bodies.push(body);}update() {this.bodies.forEach(body => {// 应用重力body.velocity.x += this.gravity.x;body.velocity.y += this.gravity.y;// 应用阻尼body.velocity.x *= this.damping;body.velocity.y *= this.damping;// 更新位置body.x += body.velocity.x;body.y += body.velocity.y;// 边界碰撞检测if (body.x - body.radius < 0) {body.x = body.radius;body.velocity.x *= -this.bounce;}if (body.x + body.radius > this.canvas.width) {body.x = this.canvas.width - body.radius;body.velocity.x *= -this.bounce;}if (body.y - body.radius < 0) {body.y = body.radius;body.velocity.y *= -this.bounce;}if (body.y + body.radius > this.canvas.height) {body.y = this.canvas.height - body.radius;body.velocity.y *= -this.bounce;}});// 简单的碰撞检测for (let i = 0; i < this.bodies.length; i++) {for (let j = i + 1; j < this.bodies.length; j++) {this.checkCollision(this.bodies[i], this.bodies[j]);}}}checkCollision(body1, body2) {const dx = body2.x - body1.x;const dy = body2.y - body1.y;const distance = Math.sqrt(dx * dx + dy * dy);const minDistance = body1.radius + body2.radius;if (distance < minDistance) {// 分离物体const overlap = minDistance - distance;const separationX = (dx / distance) * overlap * 0.5;const separationY = (dy / distance) * overlap * 0.5;body1.x -= separationX;body1.y -= separationY;body2.x += separationX;body2.y += separationY;// 计算碰撞响应const relativeVelocityX = body2.velocity.x - body1.velocity.x;const relativeVelocityY = body2.velocity.y - body1.velocity.y;const velocityAlongNormal = relativeVelocityX * (dx / distance) + relativeVelocityY * (dy / distance);if (velocityAlongNormal > 0) return;const restitution = Math.min(this.bounce, this.bounce);const impulse = -(1 + restitution) * velocityAlongNormal / (1/body1.mass + 1/body2.mass);body1.velocity.x -= impulse * (dx / distance) / body1.mass;body1.velocity.y -= impulse * (dy / distance) / body1.mass;body2.velocity.x += impulse * (dx / distance) / body2.mass;body2.velocity.y += impulse * (dy / distance) / body2.mass;}}draw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.bodies.forEach(body => {this.ctx.beginPath();this.ctx.arc(body.x, body.y, body.radius, 0, Math.PI * 2);this.ctx.fillStyle = body.color;this.ctx.fill();this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';this.ctx.stroke();});}animate() {this.update();this.draw();requestAnimationFrame(() => this.animate());}
}// 使用示例
const canvas = document.getElementById('physics-canvas');
const engine = new PhysicsEngine(canvas);// 添加物体
for (let i = 0; i < 10; i++) {engine.addBody({x: Math.random() * canvas.width,y: Math.random() * canvas.height * 0.5,radius: Math.random() * 20 + 10,velocity: { x: (Math.random() - 0.5) * 5, y: 0 },mass: 1,color: `hsl(${Math.random() * 360}, 70%, 50%)`});
}

2. SVG冷门用法实战

2.1 动态图表生成

SVG可以创建复杂的动态图表,具有矢量图形的优势:

class DynamicChart {constructor(container, options = {}) {this.container = container;this.svg = this.createSVG();this.options = {width: 800,height: 400,margin: { top: 20, right: 20, bottom: 40, left: 50 },animationDuration: 1000,...options};this.data = [];this.scales = {};this.setupChart();}createSVG() {const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');svg.setAttribute('width', this.options.width);svg.setAttribute('height', this.options.height);this.container.appendChild(svg);return svg;}setupChart() {const { width, height, margin } = this.options;// 创建主组this.mainGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');this.mainGroup.setAttribute('transform', `translate(${margin.left}, ${margin.top})`);this.svg.appendChild(this.mainGroup);// 创建坐标轴this.createAxes();// 创建图表区域this.chartArea = document.createElementNS('http://www.w3.org/2000/svg', 'g');this.mainGroup.appendChild(this.chartArea);}createAxes() {const { width, height, margin } = this.options;const innerWidth = width - margin.left - margin.right;const innerHeight = height - margin.top - margin.bottom;// X轴const xAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');xAxis.setAttribute('x1', 0);xAxis.setAttribute('y1', innerHeight);xAxis.setAttribute('x2', innerWidth);xAxis.setAttribute('y2', innerHeight);xAxis.setAttribute('stroke', '#ccc');this.mainGroup.appendChild(xAxis);// Y轴const yAxis = document.createElementNS('http://www.w3.org/2000/svg', 'line');yAxis.setAttribute('x1', 0);yAxis.setAttribute('y1', 0);yAxis.setAttribute('x2', 0);yAxis.setAttribute('y2', innerHeight);yAxis.setAttribute('stroke', '#ccc');this.mainGroup.appendChild(yAxis);// 网格线for (let i = 0; i <= 5; i++) {const y = (innerHeight / 5) * i;const gridLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');gridLine.setAttribute('x1', 0);gridLine.setAttribute('y1', y);gridLine.setAttribute('x2', innerWidth);gridLine.setAttribute('y2', y);gridLine.setAttribute('stroke', '#f0f0f0');gridLine.setAttribute('stroke-dasharray', '2,2');this.mainGroup.appendChild(gridLine);}}updateData(newData) {this.data = newData;this.updateScales();this.render();}updateScales() {const { width, height, margin } = this.options;const innerWidth = width - margin.left - margin.right;const innerHeight = height - margin.top - margin.bottom;const xExtent = [0, this.data.length - 1];const yExtent = [0, Math.max(...this.data.map(d => d.value))];this.scales.x = {domain: xExtent,range: [0, innerWidth],scale: (value) => {const ratio = (value - xExtent[0]) / (xExtent[1] - xExtent[0]);return ratio * innerWidth;}};this.scales.y = {domain: yExtent,range: [innerHeight, 0],scale: (value) => {const ratio = (value - yExtent[0]) / (yExtent[1] - yExtent[0]);return innerHeight - (ratio * innerHeight);}};}render() {// 清除现有内容while (this.chartArea.firstChild) {this.chartArea.removeChild(this.chartArea.firstChild);}// 创建柱状图this.data.forEach((d, i) => {const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');const x = this.scales.x.scale(i);const y = this.scales.y.scale(d.value);const width = this.scales.x.scale(1) - this.scales.x.scale(0) - 2;const height = this.scales.y.scale(0) - y;rect.setAttribute('x', x);rect.setAttribute('y', this.scales.y.scale(0));rect.setAttribute('width', width);rect.setAttribute('height', 0);rect.setAttribute('fill', `hsl(${(i / this.data.length) * 360}, 70%, 50%)`);rect.setAttribute('opacity', '0.8');// 动画const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');animate.setAttribute('attributeName', 'y');animate.setAttribute('from', this.scales.y.scale(0));animate.setAttribute('to', y);animate.setAttribute('dur', `${this.options.animationDuration}ms`);animate.setAttribute('fill', 'freeze');rect.appendChild(animate);const animateHeight = document.createElementNS('http://www.w3.org/2000/svg', 'animate');animateHeight.setAttribute('attributeName', 'height');animateHeight.setAttribute('from', '0');animateHeight.setAttribute('to', height);animateHeight.setAttribute('dur', `${this.options.animationDuration}ms`);animateHeight.setAttribute('fill', 'freeze');rect.appendChild(animateHeight);// 鼠标交互rect.addEventListener('mouseenter', () => {rect.setAttribute('opacity', '1');rect.setAttribute('transform', 'scale(1.05)');});rect.addEventListener('mouseleave', () => {rect.setAttribute('opacity', '0.8');rect.setAttribute('transform', 'scale(1)');});this.chartArea.appendChild(rect);});}
}// 使用示例
const chartContainer = document.getElementById('chart-container');
const chart = new DynamicChart(chartContainer);// 模拟数据更新
let dataIndex = 0;
const generateData = () => {return Array.from({ length: 10 }, (_, i) => ({name: `Item ${i + 1}`,value: Math.random() * 100}));
};setInterval(() => {chart.updateData(generateData());
}, 3000);chart.updateData(generateData());

2.2 路径动画

SVG的路径动画可以创建复杂的动画效果:

class PathAnimator {constructor(svg) {this.svg = svg;this.paths = [];this.animations = [];}createPathAnimation(pathData, duration = 2000, color = '#3498db') {const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');path.setAttribute('d', pathData);path.setAttribute('stroke', color);path.setAttribute('stroke-width', '3');path.setAttribute('fill', 'none');path.setAttribute('stroke-dasharray', '1000');path.setAttribute('stroke-dashoffset', '1000');this.svg.appendChild(path);// 创建动画const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');animate.setAttribute('attributeName', 'stroke-dashoffset');animate.setAttribute('from', '1000');animate.setAttribute('to', '0');animate.setAttribute('dur', `${duration}ms`);animate.setAttribute('fill', 'freeze');path.appendChild(animate);// 添加发光效果const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');filter.setAttribute('id', `glow-${Date.now()}`);const feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');feGaussianBlur.setAttribute('stdDeviation', '3');feGaussianBlur.setAttribute('result', 'coloredBlur');const feMerge = document.createElementNS('http://www.w3.org/2000/svg', 'feMerge');const feMergeNode1 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');feMergeNode1.setAttribute('in', 'coloredBlur');const feMergeNode2 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');feMergeNode2.setAttribute('in', 'SourceGraphic');feMerge.appendChild(feMergeNode1);feMerge.appendChild(feMergeNode2);filter.appendChild(feGaussianBlur);filter.appendChild(feMerge);this.svg.appendChild(filter);path.setAttribute('filter', `url(#glow-${Date.now()})`);animate.beginElement();return { path, animate };}createTextPathAnimation(text, pathData, duration = 5000) {const textPath = document.createElementNS('http://www.w3.org/2000/svg', 'text');textPath.setAttribute('font-size', '24');textPath.setAttribute('fill', '#e74c3c');textPath.setAttribute('font-family', 'Arial, sans-serif');const textNode = document.createTextNode(text);textPath.appendChild(textNode);const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');const pathId = `text-path-${Date.now()}`;path.setAttribute('id', pathId);path.setAttribute('d', pathData);path.setAttribute('fill', 'none');const textPathElement = document.createElementNS('http://www.w3.org/2000/svg', 'textPath');textPathElement.setAttribute('href', `#${pathId}`);textPathElement.setAttribute('startOffset', '0%');// 将文本移到textPath中textPathElement.appendChild(textNode);textPath.appendChild(textPathElement);this.svg.appendChild(path);this.svg.appendChild(textPath);// 创建动画const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');animate.setAttribute('attributeName', 'startOffset');animate.setAttribute('from', '0%');animate.setAttribute('to', '100%');animate.setAttribute('dur', `${duration}ms`);animate.setAttribute('repeatCount', 'indefinite');textPathElement.appendChild(animate);animate.beginElement();return { textPath, path, animate };}createMorphingAnimation(pathData1, pathData2, duration = 2000) {const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');path.setAttribute('d', pathData1);path.setAttribute('stroke', '#9b59b6');path.setAttribute('stroke-width', '4');path.setAttribute('fill', 'none');this.svg.appendChild(path);// 创建变形动画const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate');animate.setAttribute('attributeName', 'd');animate.setAttribute('from', pathData1);animate.setAttribute('to', pathData2);animate.setAttribute('dur', `${duration}ms`);animate.setAttribute('repeatCount', 'indefinite');animate.setAttribute('calcMode', 'spline');animate.setAttribute('keySplines', '0.4 0 0.6 1');path.appendChild(animate);animate.beginElement();return { path, animate };}
}// 使用示例
const svg = document.getElementById('path-animation-svg');
const animator = new PathAnimator(svg);// 创建路径绘制动画
const heartPath = "M213.1,6.7c-32.4-14.4-73.7,0-88.1,30.6C110.6,4.9,67.5-9.5,36.9,6.7C2.8,22.9-13.4,62.4,13.5,110.9C33.3,145.1,67.5,170.3,125,217c59.3-46.7,93.5-71.9,111.5-106.1C263.4,64.2,247.2,22.9,213.1,6.7z";
animator.createPathAnimation(heartPath, 3000, '#e74c3c');// 创建文本路径动画
const wavePath = "M10,80 Q95,10 180,80 T350,80";
animator.createTextPathAnimation("Hello SVG Animation!", wavePath, 5000);// 创建形状变形动画
const circlePath = "M50,50 A30,30 0 1,1 50,51 Z";
const starPath = "M50,10 L61,35 L90,35 L68,55 L79,80 L50,65 L21,80 L32,55 L10,35 L39,35 Z";
animator.createMorphingAnimation(circlePath, starPath, 3000);

3. 动态背景实现

3.1 波浪背景

使用Canvas创建动态波浪背景:

class WaveBackground {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.waves = [{ amplitude: 20, frequency: 0.01, speed: 0.02, offset: 0, color: 'rgba(52, 152, 219, 0.3)' },{ amplitude: 30, frequency: 0.015, speed: 0.025, offset: Math.PI / 3, color: 'rgba(46, 204, 113, 0.3)' },{ amplitude: 15, frequency: 0.008, speed: 0.018, offset: Math.PI / 2, color: 'rgba(155, 89, 182, 0.3)' }];this.animate();}drawWave(wave, time) {this.ctx.beginPath();this.ctx.moveTo(0, this.canvas.height);for (let x = 0; x <= this.canvas.width; x += 2) {const y = this.canvas.height / 2 + Math.sin(x * wave.frequency + time * wave.speed + wave.offset) * wave.amplitude +Math.sin(x * wave.frequency * 2 + time * wave.speed * 1.5) * wave.amplitude * 0.5;this.ctx.lineTo(x, y);}this.ctx.lineTo(this.canvas.width, this.canvas.height);this.ctx.lineTo(0, this.canvas.height);this.ctx.closePath();this.ctx.fillStyle = wave.color;this.ctx.fill();}animate() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);const time = Date.now() * 0.001;this.waves.forEach(wave => {this.drawWave(wave, time);});requestAnimationFrame(() => this.animate());}
}// 使用示例
const waveCanvas = document.getElementById('wave-background');
new WaveBackground(waveCanvas);

3.2 几何图形动画

创建动态的几何图形背景:

class GeometricBackground {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.shapes = [];this.createShapes();this.animate();}createShapes() {const shapeCount = 15;for (let i = 0; i < shapeCount; i++) {this.shapes.push({x: Math.random() * this.canvas.width,y: Math.random() * this.canvas.height,size: Math.random() * 50 + 20,rotation: Math.random() * Math.PI * 2,rotationSpeed: (Math.random() - 0.5) * 0.02,color: `hsla(${Math.random() * 360}, 70%, 50%, 0.1)`,type: Math.floor(Math.random() * 3), // 0: 三角形, 1: 正方形, 2: 六边形pulsePhase: Math.random() * Math.PI * 2});}}drawTriangle(shape) {this.ctx.save();this.ctx.translate(shape.x, shape.y);this.ctx.rotate(shape.rotation);this.ctx.beginPath();this.ctx.moveTo(0, -shape.size);this.ctx.lineTo(-shape.size * 0.866, shape.size * 0.5);this.ctx.lineTo(shape.size * 0.866, shape.size * 0.5);this.ctx.closePath();this.ctx.fillStyle = shape.color;this.ctx.fill();this.ctx.strokeStyle = shape.color.replace('0.1', '0.3');this.ctx.stroke();this.ctx.restore();}drawSquare(shape) {this.ctx.save();this.ctx.translate(shape.x, shape.y);this.ctx.rotate(shape.rotation);this.ctx.fillStyle = shape.color;this.ctx.fillRect(-shape.size / 2, -shape.size / 2, shape.size, shape.size);this.ctx.strokeStyle = shape.color.replace('0.1', '0.3');this.ctx.strokeRect(-shape.size / 2, -shape.size / 2, shape.size, shape.size);this.ctx.restore();}drawHexagon(shape) {this.ctx.save();this.ctx.translate(shape.x, shape.y);this.ctx.rotate(shape.rotation);this.ctx.beginPath();for (let i = 0; i < 6; i++) {const angle = (i / 6) * Math.PI * 2;const x = Math.cos(angle) * shape.size;const y = Math.sin(angle) * shape.size;if (i === 0) {this.ctx.moveTo(x, y);} else {this.ctx.lineTo(x, y);}}this.ctx.closePath();this.ctx.fillStyle = shape.color;this.ctx.fill();this.ctx.strokeStyle = shape.color.replace('0.1', '0.3');this.ctx.stroke();this.ctx.restore();}animate() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);const time = Date.now() * 0.001;this.shapes.forEach(shape => {shape.rotation += shape.rotationSpeed;shape.pulsePhase += 0.02;// 脉冲效果const pulseScale = 1 + Math.sin(shape.pulsePhase) * 0.1;const originalSize = shape.size;shape.size = originalSize * pulseScale;switch (shape.type) {case 0:this.drawTriangle(shape);break;case 1:this.drawSquare(shape);break;case 2:this.drawHexagon(shape);break;}// 恢复原始大小shape.size = originalSize;});requestAnimationFrame(() => this.animate());}
}// 使用示例
const geometricCanvas = document.getElementById('geometric-background');
new GeometricBackground(geometricCanvas);

4. 简易数据可视化

4.1 实时图表

创建一个简单的实时数据图表:

class RealtimeChart {constructor(canvas, options = {}) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.options = {maxDataPoints: 50,updateInterval: 100,lineColor: '#3498db',gridColor: '#ecf0f1',backgroundColor: '#ffffff',...options};this.data = [];this.isRunning = false;this.setupChart();}setupChart() {this.canvas.width = this.canvas.offsetWidth;this.canvas.height = this.canvas.offsetHeight;// 模拟数据生成this.dataGenerator = setInterval(() => {if (this.isRunning) {const newValue = Math.sin(Date.now() * 0.001) * 50 + 50 + (Math.random() - 0.5) * 20;this.addDataPoint(newValue);}}, this.options.updateInterval);}addDataPoint(value) {this.data.push({value: value,timestamp: Date.now()});// 保持数据点数量if (this.data.length > this.options.maxDataPoints) {this.data.shift();}this.draw();}draw() {const { width, height } = this.canvas;const { lineColor, gridColor, backgroundColor } = this.options;// 清空画布this.ctx.fillStyle = backgroundColor;this.ctx.fillRect(0, 0, width, height);// 绘制网格this.drawGrid();if (this.data.length < 2) return;// 计算比例const maxValue = Math.max(...this.data.map(d => d.value));const minValue = Math.min(...this.data.map(d => d.value));const valueRange = maxValue - minValue || 1;// 绘制数据线this.ctx.strokeStyle = lineColor;this.ctx.lineWidth = 2;this.ctx.beginPath();this.data.forEach((point, index) => {const x = (index / (this.data.length - 1)) * width;const y = height - ((point.value - minValue) / valueRange) * height * 0.8 - height * 0.1;if (index === 0) {this.ctx.moveTo(x, y);} else {this.ctx.lineTo(x, y);}});this.ctx.stroke();// 绘制数据点this.data.forEach((point, index) => {const x = (index / (this.data.length - 1)) * width;const y = height - ((point.value - minValue) / valueRange) * height * 0.8 - height * 0.1;this.ctx.fillStyle = lineColor;this.ctx.beginPath();this.ctx.arc(x, y, 3, 0, Math.PI * 2);this.ctx.fill();});// 绘制当前值if (this.data.length > 0) {const currentValue = this.data[this.data.length - 1].value;this.ctx.fillStyle = '#2c3e50';this.ctx.font = '14px Arial';this.ctx.fillText(`Current: ${currentValue.toFixed(2)}`, 10, 25);}}drawGrid() {const { width, height } = this.canvas;const { gridColor } = this.options;this.ctx.strokeStyle = gridColor;this.ctx.lineWidth = 1;// 垂直网格线for (let i = 0; i <= 10; i++) {const x = (width / 10) * i;this.ctx.beginPath();this.ctx.moveTo(x, 0);this.ctx.lineTo(x, height);this.ctx.stroke();}// 水平网格线for (let i = 0; i <= 5; i++) {const y = (height / 5) * i;this.ctx.beginPath();this.ctx.moveTo(0, y);this.ctx.lineTo(width, y);this.ctx.stroke();}}start() {this.isRunning = true;}stop() {this.isRunning = false;}destroy() {this.stop();if (this.dataGenerator) {clearInterval(this.dataGenerator);}}
}// 使用示例
const realtimeCanvas = document.getElementById('realtime-chart');
const chart = new RealtimeChart(realtimeCanvas);
chart.start();

4.2 进度指示器

创建炫酷的进度指示器:

class CircularProgress {constructor(canvas, options = {}) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.options = {radius: 80,lineWidth: 10,backgroundColor: '#ecf0f1',progressColor: '#3498db',textColor: '#2c3e50',showPercentage: true,animationDuration: 1000,...options};this.progress = 0;this.targetProgress = 0;this.animationId = null;this.draw();}setProgress(value) {this.targetProgress = Math.max(0, Math.min(100, value));this.animate();}animate() {if (this.animationId) {cancelAnimationFrame(this.animationId);}const startProgress = this.progress;const endProgress = this.targetProgress;const duration = this.options.animationDuration;const startTime = Date.now();const animateStep = () => {const elapsed = Date.now() - startTime;const progress = Math.min(elapsed / duration, 1);// 使用缓动函数const easeProgress = this.easeInOutCubic(progress);this.progress = startProgress + (endProgress - startProgress) * easeProgress;this.draw();if (progress < 1) {this.animationId = requestAnimationFrame(animateStep);}};animateStep();}easeInOutCubic(t) {return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;}draw() {const { width, height } = this.canvas;const centerX = width / 2;const centerY = height / 2;const { radius, lineWidth, backgroundColor, progressColor, textColor, showPercentage } = this.options;this.ctx.clearRect(0, 0, width, height);// 绘制背景圆环this.ctx.beginPath();this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);this.ctx.strokeStyle = backgroundColor;this.ctx.lineWidth = lineWidth;this.ctx.stroke();// 绘制进度圆环const startAngle = -Math.PI / 2;const endAngle = startAngle + (this.progress / 100) * Math.PI * 2;this.ctx.beginPath();this.ctx.arc(centerX, centerY, radius, startAngle, endAngle);this.ctx.strokeStyle = progressColor;this.ctx.lineWidth = lineWidth;this.ctx.lineCap = 'round';this.ctx.stroke();// 绘制百分比文本if (showPercentage) {this.ctx.fillStyle = textColor;this.ctx.font = 'bold 24px Arial';this.ctx.textAlign = 'center';this.ctx.textBaseline = 'middle';this.ctx.fillText(`${Math.round(this.progress)}%`, centerX, centerY);}// 绘制中心点this.ctx.beginPath();this.ctx.arc(centerX, centerY, 3, 0, Math.PI * 2);this.ctx.fillStyle = progressColor;this.ctx.fill();}
}// 使用示例
const progressCanvas = document.getElementById('progress-canvas');
const progress = new CircularProgress(progressCanvas, {radius: 100,lineWidth: 15,progressColor: '#e74c3c'
});// 模拟进度更新
let currentProgress = 0;
setInterval(() => {currentProgress = (currentProgress + Math.random() * 15) % 100;progress.setProgress(currentProgress);
}, 1000);

5. 性能优化技巧

5.1 Canvas性能优化

class CanvasOptimizer {constructor(canvas) {this.canvas = canvas;this.ctx = canvas.getContext('2d');this.isDirty = false;this.lastFrame = 0;this.fps = 60;this.frameInterval = 1000 / this.fps;this.setupOptimization();}setupOptimization() {// 启用图像平滑this.ctx.imageSmoothingEnabled = true;this.ctx.imageSmoothingQuality = 'high';// 使用离屏Canvasthis.offscreenCanvas = document.createElement('canvas');this.offscreenCtx = this.offscreenCanvas.getContext('2d');this.offscreenCanvas.width = this.canvas.width;this.offscreenCanvas.height = this.canvas.height;}// 批量绘制batchDraw(drawCalls) {this.offscreenCtx.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);drawCalls.forEach(drawCall => {drawCall(this.offscreenCtx);});this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.drawImage(this.offscreenCanvas, 0, 0);}// 智能重绘smartAnimate(renderFunction) {const currentFrame = Date.now();const deltaTime = currentFrame - this.lastFrame;if (deltaTime >= this.frameInterval) {renderFunction(this.ctx);this.lastFrame = currentFrame - (deltaTime % this.frameInterval);}requestAnimationFrame(() => this.smartAnimate(renderFunction));}// 对象池管理createObjectPool(createFunction, resetFunction, maxSize = 100) {const pool = [];let activeCount = 0;return {acquire: (...args) => {let obj;if (pool.length > 0) {obj = pool.pop();} else {obj = createFunction(...args);}activeCount++;return obj;},release: (obj) => {if (pool.length < maxSize) {resetFunction(obj);pool.push(obj);}activeCount--;},getActiveCount: () => activeCount,getPoolSize: () => pool.length};}// 内存管理cleanup() {this.offscreenCanvas = null;this.offscreenCtx = null;}
}// 使用示例
const canvas = document.getElementById('optimized-canvas');
const optimizer = new CanvasOptimizer(canvas);// 对象池示例
const particlePool = optimizer.createObjectPool((x, y) => ({ x, y, vx: 0, vy: 0, life: 1.0 }),(particle) => {particle.x = 0;particle.y = 0;particle.vx = 0;particle.vy = 0;particle.life = 1.0;}
);

5.2 SVG性能优化

class SVGOptimizer {constructor(svg) {this.svg = svg;this.defs = this.createDefs();this.useElements = new Map();this.isOptimized = false;}createDefs() {let defs = this.svg.querySelector('defs');if (!defs) {defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');this.svg.insertBefore(defs, this.svg.firstChild);}return defs;}// 重用图形元素createReusableElement(id, element) {const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');g.setAttribute('id', id);g.appendChild(element.cloneNode(true));this.defs.appendChild(g);return (x, y, scale = 1) => {const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');use.setAttribute('href', `#${id}`);use.setAttribute('x', x);use.setAttribute('y', y);use.setAttribute('transform', `scale(${scale})`);return use;};}// 批量属性更新batchAttributeUpdate(elements, attributes) {requestAnimationFrame(() => {elements.forEach(element => {Object.entries(attributes).forEach(([key, value]) => {element.setAttribute(key, value);});});});}// 智能重绘smartRedraw(changedElements) {if (!this.isOptimized) {// 首次优化this.optimizeTree();this.isOptimized = true;}changedElements.forEach(element => {element.style.willChange = 'transform';});requestAnimationFrame(() => {changedElements.forEach(element => {element.style.willChange = 'auto';});});}// 优化DOM树结构optimizeTree() {// 将静态元素分组const staticElements = this.svg.querySelectorAll('[data-static="true"]');if (staticElements.length > 0) {const staticGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');staticGroup.setAttribute('class', 'static-elements');staticElements.forEach(element => {staticGroup.appendChild(element.cloneNode(true));element.remove();});this.svg.appendChild(staticGroup);}// 将动态元素分组const dynamicElements = this.svg.querySelectorAll('[data-dynamic="true"]');if (dynamicElements.length > 0) {const dynamicGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');dynamicGroup.setAttribute('class', 'dynamic-elements');dynamicElements.forEach(element => {dynamicGroup.appendChild(element.cloneNode(true));element.remove();});this.svg.appendChild(dynamicGroup);}}// 清理资源cleanup() {this.useElements.clear();this.defs.innerHTML = '';}
}// 使用示例
const svg = document.getElementById('optimized-svg');
const optimizer = new SVGOptimizer(svg);// 创建可重用元素
const circleTemplate = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circleTemplate.setAttribute('r', '10');
circleTemplate.setAttribute('fill', '#3498db');const createCircle = optimizer.createReusableElement('circle-template', circleTemplate);// 使用可重用元素
for (let i = 0; i < 100; i++) {const circle = createCircle(Math.random() * 800, Math.random() * 600);svg.appendChild(circle);
}

6. 实战项目:动态背景库

6.1 完整的动态背景库

class DynamicBackgroundLibrary {constructor() {this.backgrounds = new Map();this.activeBackground = null;}// 粒子背景createParticleBackground(canvas, options = {}) {const config = {particleCount: 100,connectionDistance: 100,particleColor: '#3498db',connectionColor: 'rgba(52, 152, 219, 0.1)',interactive: true,...options};return new ParticleSystem(canvas, config);}// 波浪背景createWaveBackground(canvas, options = {}) {const config = {waveCount: 3,amplitude: 50,frequency: 0.01,speed: 0.02,colors: ['rgba(52, 152, 219, 0.3)', 'rgba(46, 204, 113, 0.3)', 'rgba(155, 89, 182, 0.3)'],...options};return new WaveBackground(canvas, config);}// 几何背景createGeometricBackground(canvas, options = {}) {const config = {shapeCount: 15,shapeTypes: ['triangle', 'square', 'hexagon'],rotationSpeed: 0.01,pulseEnabled: true,colors: ['hsla(200, 70%, 50%, 0.1)', 'hsla(120, 70%, 50%, 0.1)', 'hsla(280, 70%, 50%, 0.1)'],...options};return new GeometricBackground(canvas, config);}// 星空背景createStarfieldBackground(canvas, options = {}) {const config = {starCount: 200,starSize: { min: 1, max: 3 },starSpeed: { min: 0.1, max: 0.5 },starColor: '#ffffff',twinkleEffect: true,...options};return new StarfieldBackground(canvas, config);}// 激活背景activateBackground(type, canvas, options = {}) {if (this.activeBackground) {this.activeBackground.destroy();}let background;switch (type) {case 'particles':background = this.createParticleBackground(canvas, options);break;case 'waves':background = this.createWaveBackground(canvas, options);break;case 'geometric':background = this.createGeometricBackground(canvas, options);break;case 'starfield':background = this.createStarfieldBackground(canvas, options);break;default:throw new Error(`Unknown background type: ${type}`);}this.activeBackground = background;this.backgrounds.set(type, background);return background;}// 获取当前背景getActiveBackground() {return this.activeBackground;}// 销毁所有背景destroyAll() {this.backgrounds.forEach(background => {if (background.destroy) {background.destroy();}});this.backgrounds.clear();this.activeBackground = null;}
}// 使用示例
const backgroundLib = new DynamicBackgroundLibrary();
const canvas = document.getElementById('background-canvas');// 激活粒子背景
const particleBg = backgroundLib.activateBackground('particles', canvas, {particleCount: 150,particleColor: '#e74c3c',interactive: true
});// 切换背景类型
document.getElementById('background-selector').addEventListener('change', (e) => {backgroundLib.activateBackground(e.target.value, canvas);
});

总结

Canvas和SVG的冷门用法为前端开发提供了无限的创意空间。通过本文介绍的技术,你可以:

  1. 创建炫酷的动态背景 - 粒子系统、波浪效果、几何动画等
  2. 实现音频可视化 - 结合Web Audio API创建音乐播放器
  3. 处理图像和滤镜 - 实时图像处理和特效
  4. 模拟物理效果 - 重力、碰撞等物理引擎
  5. 生成动态图表 - SVG的矢量优势和动画能力
  6. 优化性能 - 对象池、批量操作、智能重绘等

这些技术不仅可以提升用户体验,还能为你的项目增添独特的视觉效果。记住,性能优化是关键,合理使用这些技术才能发挥它们的最大价值。

http://www.dtcms.com/a/585017.html

相关文章:

  • 昆明做网站做的好的公司智能建站系统 网站建设的首选
  • kali安装npm/sourcemap
  • 协作机器人的关节是通过什么工艺加工的
  • 轻松开启数字化时代,一键部署实现CRM落地
  • 长春市网站推广网站开发技术人员
  • JavaScript 指南
  • C++ LeetCode 力扣刷题 541. 反转字符串 II
  • C++死锁深度解析:从成因到预防与避免
  • 达梦DMDSC知识
  • 【C++】基于C++的RPC分布式网络通信框架(二)
  • Python 实现:从数学模型到完整控制台版《2048》游戏
  • 第1课-通过DIFY实现一个完整的Text2Sql来讲AI原生及Agentic RAG长什么样
  • 站长平台wordpress调用分类产品
  • 2.3 Transformer 变体与扩展:BERT、GPT 与多模态模型
  • 《Windows 服务器 ×WinSCP 保姆级配置指南:从 0 到 1 实现 “无痛” 远程文件管理》
  • 用nas做网站泰安集团
  • 自己做的网站可以运营不wordpress和json
  • 怎么做文学动漫网站公司logo设计图片欣赏
  • 网站建设 模块厦门网站建设哪家不错
  • 深圳做高端网站建设公司做家装的网站有什么不同
  • 武威网站建设DS716 II 做网站
  • 网站开发授权书如何更换网站域名
  • 做企业网站的轻量级cms一站式互联网营销平台
  • 长沙产品网站建设国外哪些网站可以注册域名
  • 汕头自助建站系统手机建网站
  • 网站建设教学后记宜昌市高新区建设局网站
  • 山西网站建设排名深圳自适应网站的公司
  • wordpress 安装中文字体如何为网站做seo体检
  • 国内高端网站定制江山建设工程信息网站
  • 皖icp合肥网站开发公司车身广告设计图片