<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HTML5 canvas水波纹动画特效</title> <style type="text/css"> body{ margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; /*background: transparent;*/ box-sizing: border-box; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); //为了防止ios系统出现点击闪烁现象 } body { overflow: hidden; } #holder{ width: 100%; height: 6.3rem; position: absolute; /* cursor: pointer; */ text-align: center; } </style> </head> <body id="body"> <div id="holder"></div> <script> window.onload = ()=>{ let outer = document.getElementById(‘holder‘).getBoundingClientRect(); //获取canvas,外部容器的宽高,来进行设置canvas的宽高 console.log(outer) function WaterRipple(element, settings) { // 默认设置 var defaults = { image: "", dropRadius: 3, // 波源半径大小 width: ‘‘, height: ‘‘, delay: 1, attenuation: 3, maxAmplitude: 1024, // sourceAmplitude: 512, // 震源振幅 sourceAmplitude: 256, // 震源振幅 auto: false }; // 合并设置 for (var item in defaults) { if (!settings.hasOwnProperty(item)) { settings[item] = defaults[item] } } // 检测背景图 if (!settings.image.length) { return false; } var width = settings.width, height = settings.height, dropRadius = settings.dropRadius, delay = settings.delay * 1000, attenuation = settings.attenuation, // 衰减级别 maxAmplitude = settings.maxAmplitude, // 最大振幅 sourceAmplitude = settings.sourceAmplitude, half_width = width >> 1, half_height = height >> 1, amplitude_size = width * (height + 2) * 2, old_index = width, new_index = width * (height + 3), map_index, // 振幅数组索引 texture, // 原始图像像素信息 ripple, // 参数波纹的图像像素信息 image, // Image对象 autoRepeat, // 自动产生波源的重复事件 ripple_map = [], last_map = []; var canvas = document.createElement(‘canvas‘); canvas.style.width = outer.width+‘px‘; canvas.style.height = outer.height+‘px‘; canvas.width = width; canvas.height = height; element.appendChild(canvas); var ctx = canvas.getContext(‘2d‘); ctx.fillStyle = settings.bgColor; ctx.fillRect(0, 0, width, height); window.requestAnimationFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); // 加载图片,注意图片不要跨域 function loadImage() { image = new Image(); // image.src = settings.image; // image.src = ‘./wallet_top.png‘; image.src = ‘./long.jpg‘; // image.src = ‘./top@3x.png‘; // image.src = ‘./girl.png‘; image.crossOrigin = ‘anonymous‘; image.onload = function() { console.log(image.style) init(); } } // 保存图像的所有像素信息 //将图片绘制在图片尺寸的宽高上 function saveImageData() { // 在canvas中绘制图形 console.log(‘width‘,width) ctx.drawImage(image, 0, 0,width,height); // ctx.drawImage(image, 0, 0,375,315); // 图像的ImageData对象 texture = ctx.getImageData(0, 0, width, height); ripple = ctx.getImageData(0, 0, width, height); } function init() { saveImageData(); // 波幅数组初始化为0 for (var i = 0; i < amplitude_size; i++) { ripple_map[i] = last_map[i] = 0; } animate(); // 如果设置了自动产生波源,则随机参数波源 console.log(‘width‘,width) if (settings.auto) { autoRepeat = setInterval(function() { disturb(Math.random() * width, Math.random() * height); }, delay); disturb(Math.random() * width, Math.random() * height); } } // 动画主循环 function animate() { requestAnimationFrame(animate); renderRipple(); } // 在指定地点产生波源 function disturb(circleX, circleY) { // 将值向下取整 circleX <<= 0; circleY <<= 0; var maxDistanceX = circleX + dropRadius, maxDistanceY = circleY + dropRadius; for (var y = circleY - dropRadius; y < maxDistanceY; y++) { for (var x = circleX - dropRadius; x < maxDistanceX; x++) { ripple_map[old_index + y * width + x] += sourceAmplitude; } } // console.log(‘ripple_map‘,ripple_map) } // 渲染下一帧 function renderRipple() { var i = old_index, deviation_x, // x水平方向偏移 deviation_y, // y竖直方向偏移 pixel_deviation, // 偏移后的ImageData对象像素索引 pixel_source; // 原始ImageData对象像素索引 // 交互索引 old_index, new_index old_index = new_index; new_index = i; // 设置像素索引和振幅索引 i = 0; map_index = old_index; // 使用局部变量优化全局作用域查询 var _map_index = map_index, _width = width, _height = height, _half_width = half_width, _half_height = half_height, _ripple_map = ripple_map, _last_map = last_map, _ripple_data = ripple.data, // 引用修改 _texture_data = texture.data, // 引用修改 _new_index = new_index, _attenuation = attenuation, _maxAmplitude = maxAmplitude; // 渲染所有像素点 for (var y = 0; y < _height; y++) { for (var x = 0; x < _width; x++) { var x_boundary = 0, judge = _map_index % _width; if (judge == 0) { x_boundary = 1; // 左边边界 }else if (judge == _width - 1) { x_boundary = 2; // 右边边界 } var top = _ripple_map[_map_index - _width],// 上边的相邻点 bottom = _ripple_map[_map_index + _width],// 下边的相邻点 left = x_boundary != 1 ? _ripple_map[_map_index - 1] : 0,// 左边的相邻点 right = x_boundary != 2 ? _ripple_map[_map_index + 1] : 0;// 右边的相邻点 // 计算当前像素点下一时刻的振幅 var amplitude = (top + bottom + left + right) >> 1; amplitude -= _ripple_map[_new_index + i]; amplitude -= amplitude >> _attenuation; // 计算衰减 // 更新振幅数组 _ripple_map[_new_index + i] = amplitude; amplitude = _maxAmplitude - amplitude; var old_amplitude = _last_map[i]; _last_map[i] = amplitude; if (old_amplitude != amplitude) { deviation_x = (((x - _half_width) * amplitude / _maxAmplitude) << 0) + _half_width; deviation_y = (((y - _half_height) * amplitude / _maxAmplitude) << 0) + _half_height; // 检查边界 if (deviation_x > _width) { deviation_x = _width - 1; } if (deviation_x < 0) { deviation_x = 0; } if (deviation_y > _height) { deviation_y = _height - 1; } if (deviation_y < 0) { deviation_y = 0; } pixel_source = i * 4; // console.error(width) pixel_deviation = (deviation_x + (deviation_y * width)) * 4; // 移动像素的RGBA信息 _ripple_data[pixel_source] = _texture_data[pixel_deviation]; _ripple_data[pixel_source + 1] = _texture_data[pixel_deviation + 1]; _ripple_data[pixel_source + 2] = _texture_data[pixel_deviation + 2]; // ripple.data[pixel_source + 3] = texture.data[pixel_deviation + 3]; } ++i; ++_map_index; } } map_index = _map_index; ctx.putImageData(ripple, 0, 0); } function calculAmplitude(index, old_amplitude) { var x_boundary = 0, judge = map_index % width; if (judge == 0) { x_boundary = 1; // 左边边界 }else if (judge == width - 1) { x_boundary = 2; // 右边边界 } var top = ripple_map[index - width],// 上边的相邻点 bottom = ripple_map[index + width],// 下边的相邻点 left = x_boundary != 1 ? ripple_map[index - 1] : 0,// 左边的相邻点 right = x_boundary != 2 ? ripple_map[index + 1] : 0;// 右边的相邻点 // 计算当前像素点下一时刻的振幅 var amplitude = top + bottom + left + right; amplitude >>= 1; amplitude -= old_amplitude; amplitude -= amplitude >> attenuation; // 计算衰减 return amplitude; } this.disturb = function(a, b) { console.log(‘ab‘,a) console.log(‘ab‘,b) disturb(a, b); // disturb(573, 759); }; loadImage(); return this; } function init() { //Settings - params for WaterRippleEffect var settings = { //注意图片不要跨域 image: ‘./girl.png‘,//image path // image: ‘./wallettop.png‘,//image path dropRadius: 3,//radius of the ripple //这里设置canvas画布的宽高,可以使用图片的宽高来设置,这样会保证图片清晰程度。 width: 750,//width height: 630,//height // width: outer.width,//width // height: outer.height,//height delay: 1,//if auto param === true. 1 === 1 second delay for animation auto: true,//if auto param === true, animation starts on it′s own attenuation:5, }; //init var waterRippleEffect = new WaterRipple( document.getElementById( ‘holder‘ ), settings ); // document.getElementById( ‘holder‘ ).style.cursor = ‘pointer‘; //on click document.getElementById( ‘holder‘ ).addEventListener( ‘click‘, function( e ) { var mouseX = e.layerX; var mouseY = e.layerY; console.log(‘e‘,e) let canvas_x = (mouseX/outer.width)*settings.width; let canvas_y = (mouseY/outer.height)*settings.height; // waterRippleEffect.disturb( mouseX, mouseY ); waterRippleEffect.disturb( canvas_x, canvas_y ); } ); //on mousemove // document.getElementById( ‘holder‘ ).addEventListener( ‘mousemove‘, function( e ) { // var mouseX = e.layerX; // var mouseY = e.layerY; // // waterRippleEffect.disturb( mouseX, mouseY ); // } ); document.onkeydown = function(e) { var event = e || window.event || arguments.callee.caller.arguments[0]; if(event && event.keyCode==13){ // enter 键 waterRippleEffect.disturb( 200, 200); } } } init(); } </script> </body>
主要做了以下几个部分:
1、动态设置canvas的宽高,使其自适应移动端的尺寸
2、动态设置波纹出现的位置,
document.getElementById( ‘holder‘ ).addEventListener( ‘click‘, function( e ) {
var mouseX = e.layerX;
var mouseY = e.layerY;
console.log(‘e‘,e)
//鼠标或者手指在画布css尺寸上的位置来乘以canvas画布的位置来精确生成波纹。
let canvas_x = (mouseX/outer.width)*settings.width;
let canvas_y = (mouseY/outer.height)*settings.height;
// waterRippleEffect.disturb( mouseX, mouseY );
waterRippleEffect.disturb( canvas_x, canvas_y );
} );