在html5中实现动态波纹滤镜效果,核心是通过实时修改像素数据或者 shader 计算来模拟波纹的光学变化,常用的实现方案有canvas 2D像素操作和WebGL shader两种,下面分别介绍具体的实现步骤。

方案一:基于canvas 2D的动态波纹滤镜
实现原理
通过getImageData获取canvas中图像的原始像素数据,根据波纹的数学公式(正弦波叠加)计算每个像素的偏移量,重新映射像素位置后生成新的图像数据,再通过requestAnimationFrame循环更新实现动态效果。
具体步骤
- 创建canvas元素并获取2D上下文,加载需要添加滤镜的图像资源
- 定义波纹相关的参数,包括波纹频率、振幅、动画时间偏移量
- 编写波纹偏移计算函数,根据像素坐标和时间计算新的像素位置
- 在动画循环中不断更新波纹参数,重新绘制图像实现动态效果
完整代码示例
// 获取canvas和上下文
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
// 设置canvas尺寸
canvas.width = 800;
canvas.height = 600;
// 加载图像
const img = new Image();
img.src = 'https://ipipp.com/sample.jpg';
img.onload = () => {
// 初始绘制图像
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 开始动画循环
animate();
};
// 波纹参数
let time = 0;
const waveFrequency = 0.02; // 波纹频率
const waveAmplitude = 10; // 波纹振幅
const waveSpeed = 0.05; // 波纹速度
// 动画循环函数
function animate() {
// 获取原始图像像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 创建新的像素数据数组
const newData = ctx.createImageData(canvas.width, canvas.height);
const newDataArr = newData.data;
// 遍历每个像素计算偏移
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
// 计算波纹偏移量,使用正弦波叠加时间参数
const offsetX = Math.sin(y * waveFrequency + time) * waveAmplitude;
const offsetY = Math.cos(x * waveFrequency + time) * waveAmplitude;
// 计算原始像素坐标,处理边界
const srcX = Math.min(Math.max(Math.floor(x + offsetX), 0), canvas.width - 1);
const srcY = Math.min(Math.max(Math.floor(y + offsetY), 0), canvas.height - 1);
// 获取原始像素索引
const srcIndex = (srcY * canvas.width + srcX) * 4;
// 获取目标像素索引
const destIndex = (y * canvas.width + x) * 4;
// 复制像素值
newDataArr[destIndex] = data[srcIndex];
newDataArr[destIndex + 1] = data[srcIndex + 1];
newDataArr[destIndex + 2] = data[srcIndex + 2];
newDataArr[destIndex + 3] = data[srcIndex + 3];
}
}
// 绘制新的像素数据
ctx.putImageData(newData, 0, 0);
// 更新时间参数
time += waveSpeed;
// 请求下一帧动画
requestAnimationFrame(animate);
}
方案二:基于WebGL的动态波纹滤镜
实现原理
WebGL通过顶点着色器和片段着色器直接操作GPU渲染流程,在片段着色器中根据波纹公式实时计算每个像素的采样偏移,性能远高于canvas 2D的像素操作方式,适合处理大尺寸图像或者高帧率需求场景。
具体步骤
- 创建WebGL上下文,编写顶点着色器和片段着色器代码
- 加载纹理图像,绑定到WebGL纹理单元
- 定义波纹相关的uniform变量,在动画循环中更新时间参数
- 持续渲染实现动态波纹效果
完整代码示例
// 创建canvas并获取WebGL上下文
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
document.body.appendChild(canvas);
canvas.width = 800;
canvas.height = 600;
// 顶点着色器代码
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
// 片段着色器代码,实现波纹效果
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
uniform float u_time;
uniform float u_frequency;
uniform float u_amplitude;
varying vec2 v_texCoord;
void main() {
// 计算波纹偏移
vec2 offset = vec2(
sin(v_texCoord.y * u_frequency + u_time) * u_amplitude,
cos(v_texCoord.x * u_frequency + u_time) * u_amplitude
);
// 采样纹理颜色
vec4 color = texture2D(u_texture, v_texCoord + offset);
gl_FragColor = color;
}
`;
// 编译着色器函数
function compileShader(gl, source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('着色器编译失败:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// 编译链接着色器程序
const vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('着色器程序链接失败:', gl.getProgramInfoLog(program));
}
gl.useProgram(program);
// 设置顶点和纹理坐标数据
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1
]), gl.STATIC_DRAW);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
1, 0,
0, 1,
1, 1
]), gl.STATIC_DRAW);
// 绑定顶点属性
const positionLocation = gl.getAttribLocation(program, 'a_position');
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// 创建纹理并加载图像
const texture = gl.createTexture();
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'https://ipipp.com/sample.jpg';
img.onload = () => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 开始动画
animate();
};
// 获取uniform变量位置
const timeLocation = gl.getUniformLocation(program, 'u_time');
const frequencyLocation = gl.getUniformLocation(program, 'u_frequency');
const amplitudeLocation = gl.getUniformLocation(program, 'u_amplitude');
// 波纹参数
let time = 0;
const frequency = 20.0;
const amplitude = 0.02;
// 动画循环
function animate() {
time += 0.05;
// 设置uniform变量
gl.uniform1f(timeLocation, time);
gl.uniform1f(frequencyLocation, frequency);
gl.uniform1f(amplitudeLocation, amplitude);
// 绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(animate);
}
两种方案对比
| 对比项 | canvas 2D方案 | WebGL方案 |
|---|---|---|
| 实现难度 | 较低,无需掌握着色器语法 | 较高,需要掌握GLSL着色器语法 |
| 性能表现 | 较差,处理大图像时帧率下降明显 | 优异,GPU加速渲染,适合高帧率场景 |
| 适用场景 | 小尺寸图像、简单波纹效果需求 | 大尺寸图像、视频滤镜、复杂波纹效果需求 |
注意事项
- canvas 2D方案中修改像素数据属于CPU操作,图像尺寸超过1000*1000时建议降低波纹计算的采样精度提升性能
- WebGL方案加载跨域图像时需要服务器配置CORS头,否则纹理无法正确读取
- 动态波纹的振幅和频率参数需要根据实际显示效果调整,避免出现过度扭曲的情况
html5动态波纹滤镜canvasrequestAnimationFrameWebGL修改时间:2026-06-13 09:54:53