canvas 与svg
一、应用
分享二维码海报、电子签名、加载动画、粒子系统、echarts
二、概念
canvas 基于像素的绘图技术,通过JS动态绘制图形,适合大量图形渲染(游戏、数据可视化);
svg 基于矢量的标记语言,支持DOM操作和事件绑定,适合交互式图形(图表、图表)
C性能更高,无DOM结构;S可访问性更好,复杂场景性能较低;C动态,S适合静态或少动态;
性能;可访问性;动态
三、操作
矩形 圆形 路径 文本
四、性能
1、Retina 高清屏 模糊问题
逻辑像素与设备物理像素不匹配
CSS像素(逻辑像素) width:100px
物理像素(设备像素)屏幕实际像素点
function adaptDPR() { const dpr = window.devicePixelRatio || 1; canvas.width = canvas.clientWidth * dpr;//实际像素数量(影响清晰度) canvas.height = canvas.clientHeight * dpr; canvas.style.width = `${canvas.clientWidth}px`;//页面显示大小(影响布局) canvas.style.height = `${canvas.clientHeight}px`; ctx.scale(dpr, dpr); // 缩放坐标系 } adaptDPR();
2、cavas渲染优化手段
减少重绘区域 clearRect();批量绘制 beginPath();缓存结果 offscreenCavas;避免复杂 scale;优化图片:预加载 drawImage()
3、跨域图片
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'https://example.com/image.png';
五、案例
1、基础图形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="myCanvas" width="900" height="200" style="background-color: pink;display: block;"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 获取 2D 上下文
// 矩形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 100, 100);
// 字体
ctx.font = '20px Arial'; // 设置字体
ctx.fillStyle = 'blue';
ctx.textAlign = 'center'; // 对齐方式
ctx.fillText('Hello Canvas', 100, 50); // 填充文本
// 半圆
ctx.beginPath();
ctx.arc(100, 100, 25, 0, Math.PI);
ctx.stroke(); // 描边
ctx.fillStyle = 'yellow'; // 填充
ctx.fill()
// 交互点
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fill();
});
// 柱状图
const data = [10, 30, 50, 20];
const barWidth = 40;
ctx.fillStyle = 'orange'; // 填充
data.forEach((height, i) => {
ctx.fillRect(50 + i * 60, 100 - height, barWidth, height);
});
</script>
</body>
</html>
2、动画-弹力球
<canvas id="myCanvas" width="400" height="300"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const ball = {
x: 100,
y: 50,
radius: 20,
speedX: 3,
speedY: 4,
gravity: 0.2
};
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新位置
ball.speedY += ball.gravity;
ball.x += ball.speedX;
ball.y += ball.speedY;
// 边界检测
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
ball.speedX = -ball.speedX;
}
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius;
ball.speedY = -ball.speedY * 0.8; // 能量损耗
}
// 绘制
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
requestAnimationFrame(animate);
}
animate();
</script>
3、动画-粒子效果
<canvas id="particles" width="200" height="300"></canvas>
<script>
// 粒子效果
const canvas = document.getElementById('particles');
const ctx = canvas.getContext('2d');
const particles = [];
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 - 1.5;
this.speedY = Math.random() * 3 - 1.5;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.x < 0 || this.x > canvas.width) this.speedX *= -1;
if (this.y < 0 || this.y > canvas.height) this.speedY *= -1;
}
draw() {
ctx.fillStyle = 'pink';
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
function init() {
for (let i = 0; i < 100; i++) {
particles.push(new Particle());
}
}
function animate() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach(particle => {
particle.update();
particle.draw();
});
requestAnimationFrame(animate);
}
init();
animate();
</script>
4、图像灰度化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 图像灰度化</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial, sans-serif;
}
canvas {
margin-top: 20px;
border: 1px solid #ccc;
max-width: 100%;
}
button {
margin-top: 10px;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
</style>
</head>
<body>
<h1>Canvas 图像灰度化</h1>
<input type="file" id="upload" accept="image/*" />
<button id="grayscale">转换为灰度图</button>
<button id="reset">重置</button>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const upload = document.getElementById('upload');
const grayscaleBtn = document.getElementById('grayscale');
let img = new Image();
// 1. 监听文件上传
upload.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
// 2. 图片加载后绘制到 Canvas
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
};
// 3. 点击按钮进行灰度化处理
grayscaleBtn.addEventListener('click', () => {
// 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 遍历每个像素(RGBA,每4个值代表一个像素)
for (let i = 0; i < data.length; i += 4) {
// 计算灰度值(R、G、B 的平均值)
// const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
// 人类眼睛对绿光更敏感,对蓝光更不敏感
const avg = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
// 设置 R、G、B 为灰度值(Alpha 通道不变)
data[i] = data[i + 1] = data[i + 2] = avg;
}
// 将处理后的像素数据重新绘制到 Canvas
ctx.putImageData(imageData, 0, 0);
});
let originalImageData;
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
};
document.getElementById('reset').addEventListener('click', () => {
ctx.putImageData(originalImageData, 0, 0);
});
</script>
</body>
</html>
六、tips
1、设备像素比 devicePixelRatio dpr
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;*//实际像素数量(影响清晰度)
2、透明度
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
//半透明实现拖尾效果 0-完全透明 1-完整颜色
ctx.fillRect(0, 0, canvas.width, canvas.height);
七、svg
canvas 大数据量 性能 游戏
svg logo icon 响应式设计 交互地图
一、定义
Scalable Vector Graphics 可伸缩矢量图形 XML矢量图形格式
不同分辨率不失真,XML,方便读取和修改
二、图形
图形 路径 文本 动画 交互
三、优化
1、压缩
2、图形复用 use defs
3、CSS变换和动画替代JS的DOM操作
四、案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg width="300" height="200" viewBox="0 0 300 200">
<!-- 坐标轴 -->
<line x1="30" y1="150" x2="270" y2="150" stroke="black" />
<line x1="30" y1="150" x2="30" y2="30" stroke="black" />
<!-- 柱状图(动态数据) -->
<rect x="50" y="100" width="40" height="50" fill="orange" />
<rect x="110" y="70" width="40" height="80" fill="green" />
<rect x="170" y="120" width="40" height="30" fill="purple" />
<!-- 标签 -->
<text x="70" y="170" text-anchor="middle">A</text>
<text x="130" y="170" text-anchor="middle">B</text>
<text x="190" y="170" text-anchor="middle">C</text>
</svg>
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 定义渐变 -->
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="red" />
<stop offset="100%" stop-color="blue" />
</linearGradient>
</defs>
<!-- 应用渐变 -->
<rect x="50" y="50" width="100" height="100" fill="url(#grad)" />
</svg>
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 定义圆形路径 -->
<path id="textPath" d="M20,100 A80,80 0 1,1 180,100 A80,80 0 1,1 20,100" fill="none" />
<!-- 文字沿路径排列 -->
<text font-size="16">
<textPath href="#textPath" startOffset="25%">
SVG文本沿路径排列效果
</textPath>
</text>
</svg>
<svg width="200" height="200" viewBox="0 0 200 200">
<circle id="circle" cx="100" cy="100" r="50" fill="red" onclick="changeColor()" />
</svg>
<script>
function changeColor() {
const circle = document.getElementById('circle');
circle.setAttribute('fill', circle.getAttribute('fill') === 'red' ? 'green' : 'red');
}
</script>
<svg width="200" height="200" viewBox="0 0 200 200">
<rect x="80" y="80" width="40" height="40" fill="blue">
<animateTransform attributeName="transform" type="rotate" from="0 100 100" to="360 100 100" dur="5s"
repeatCount="indefinite" />
</rect>
</svg>
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 脸 -->
<circle cx="100" cy="100" r="80" fill="yellow" stroke="black" stroke-width="2" />
<!-- 眼睛 -->
<ellipse cx="70" cy="80" rx="15" ry="20" fill="black" />
<ellipse cx="130" cy="80" rx="15" ry="20" fill="black" />
<!-- 嘴巴(路径) -->
<path d="M60 120 Q100 160 140 120" stroke="black" stroke-width="3" fill="none" />
</svg>
</body>
</html>
115

被折叠的 条评论
为什么被折叠?



