水滴效果实现图片切换
用户点击,从点击位置开始,类似湖水中波浪散开效果来切换下一张图片。
html代码如下:
<div id="wrap">
<div id="bg"></div>
<canvas></canvas>
</div>
实现思路是
- canvas中画出第一图片
- 在画布背后再用div#bg中放入第二张图片
- 用户点击后,画布慢慢透明,div中图片显示
- 画布透明完成后,立即在画布画出第二张图片,将下一张图片放入div#bg中
这里有需要注意的是,div#bg需要和画布一样大小,并且画布层级高于div。画布中图片居中处理canvas中图片大小自适应
直接上详情
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>水滴切换图片</title>
</head>
<body>
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#wrap {
height: 100%;
width: 100%;
overflow: hidden;
}
#wrap img {
max-width: 100%;
max-height: 100%;
}
#wrap #bg {
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
canvas {
position: absolute;
left: 0;
top: 0;
}
</style>
<div id="wrap">
<div id="bg"></div>
<canvas></canvas>
</div>
<script>
const bg = document.getElementById("bg")
function renderBg(img) {
const p = bg.children;
p && p.length > 0 ? bg.replaceChild(img, p[0]) : bg.appendChild(img);
}
const canvas = document.querySelector("canvas"), ctx = canvas.getContext('2d');
const w = canvas.width = document.documentElement.clientWidth,
h = canvas.height = document.documentElement.clientHeight;
const max = Math.floor(Math.sqrt(w * w + h * h)) + 1; // 对角线长度
const radius = 10, // 初始半径
step = 10; // 速率
let currentIndex = 0, // 当前图片索引
status = "stop" // 动画状态 stop-已完成 running-执行中
window.onload = () => {
const pic = ["./1.jpg", "./2.jpg", "./5.jpg"] // 图片换成自己图片地址
const allImage = pic.map((url) => {
const img = new Image()
img.src = url
return new Promise((resolve, reject) => {
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(img)
}
})
})
allImage.length > 0 && Promise.all(allImage).then(readyRender).catch((msg) => {
console.log('图片加载失败', msg)
});
}
function readyRender(imageArray = []) {
const len = imageArray.length;
const update = () => {
const next = (currentIndex + 1) % len;
renderBg(imageArray[next])
const img = imageArray[currentIndex], local = calculate(img.width, img.height)
ctx.fillStyle = 'white';
ctx.fill();
ctx.drawImage(img, local.px, local.py, local.pw, local.ph)
}
update()
canvas.addEventListener("click", (ev = event) => {
if (status == "running") return;
animationRadius(ev.layerX, ev.layerY, radius, () => {
currentIndex = (currentIndex + 1) % len;
update()
status = "stop"
})
status = "running"
})
}
// 波浪动画 起点坐标(x,y) 半径 radius
function animationRadius(x, y, radius, cb) {
const loop = arguments.callee
ctx.save();
ctx.beginPath()
ctx.globalCompositeOperation = 'destination-out'
ctx.arc(x, y, radius, 0, Math.PI * 2)
ctx.fill()
ctx.restore();
if (radius < max) {
return requestAnimationFrame(() => {
loop(x, y, radius + step, cb)
})
}
cb && cb();
}
// 计算出图片画在canvas中的四个参数
function calculate(pw, ph) {
var px = 0;
var py = 0;
if (pw < w && ph < h) {
px = 0.5 * w - 0.5 * pw;
py = 0.5 * h - 0.5 * ph;
} else if (ph / pw > h / w) {
var uu = ph;
ph = h
pw = pw * h / uu
px = 0.5 * w - 0.5 * pw;
} else {
var uu = pw;
pw = w;
ph = ph * pw / uu
py = 0.5 * h - 0.5 * ph;
}
return {px, py, pw, ph}
}
</script>
</body>
</html>