效果一:(css+dom实现)
代码:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>飘动的旗帜~</title> <style> * { margin: 0; padding: 0; } html, body { height: 100%; 100%; background-color: lightgrey; } body { text-align: center; position: relative; } ul, li { list-style: none; } #flag { position: absolute; left: 50%; top: 50%; transform: translate3d(-50%, -50%, 0); animation: flag-reverse ease-in-out infinite; } /* 这里是核心css样式 */ #flag>li { height: 100%; float: left; background-image: url("https://oscimg.oschina.net/oscnet/4073613a5ab7bb799829aa83a67e9f46b7d.jpg"); background-size: auto 100%; animation: flag ease-in-out infinite; } </style> </head> <body> <ul id="flag"></ul> <script> (function () { // 这里是js代码 var flagEle = document.getElementById('flag') var image = new Image() image.src = 'https://oscimg.oschina.net/oscnet/4073613a5ab7bb799829aa83a67e9f46b7d.jpg' var IMG_MAX_WIDTH = 600 var IMG_MAX_HEIGHT = 600 var imgHeight var imgWidth image.onload = function () { imgWidth = image.width imgHeight = image.height var ratio = image.width / image.height if (imgWidth > IMG_MAX_WIDTH) { imgWidth = IMG_MAX_WIDTH imgHeight = imgWidth / ratio } if (imgHeight > IMG_MAX_HEIGHT) { imgHeight = IMG_MAX_HEIGHT imgWidth = imgHeight * ratio } flagEle.style.width = imgWidth + 'px' flagEle.style.height = imgHeight + 'px' flagEle.style.marginLeft = -imgWidth / 2 + 'px' flagEle.style.marginTop = -imgHeight / 2 + 'px' splitImg(100, 20, 1.5, 2) function splitImg(sliceCount, amplitude, period, duration) { var styleEle = document.createElement('style') // styleEle.innerHTML = 'body{background: red}' var styleHtmlAry = [] var sliceCountPerPeriod = Math.floor(sliceCount / period) var sliceWidth = imgWidth / sliceCount var formula = sliceCountPerPeriod + 'n+' var interval = duration * period / sliceCount // 添加动画延时 for (var i = 0; i < sliceCount; i++) { if (i < sliceCountPerPeriod) { styleHtmlAry.push('#flag > li:nth-child(' + formula + i + ') { ') styleHtmlAry.push('animation-delay: -' + (interval * (sliceCountPerPeriod - i)) + 's;') styleHtmlAry.push('}') } styleHtmlAry.push('#flag > li:nth-child(' + i + ') { background-position: -' + (i * sliceWidth) + 'px 0; }') // 设置切片背景 } // 添加关键帧动画 styleHtmlAry.push('@keyframes flag {') styleHtmlAry.push('0% { transform: translate3d(0, ' + amplitude + 'px, 0); }') styleHtmlAry.push('50% { transform: translate3d(0, -' + amplitude + 'px, 0); }') styleHtmlAry.push('100% { transform: translate3d(0, ' + amplitude + 'px, 0); }') styleHtmlAry.push('}') // 添加反向关键帧动画 styleHtmlAry.push('@keyframes flag-reverse {') styleHtmlAry.push('0% { transform: translate3d(0, ' + (-amplitude) + 'px, 0); }') styleHtmlAry.push('50% { transform: translate3d(0, ' + amplitude + 'px, 0); }') styleHtmlAry.push('100% { transform: translate3d(0, ' + (-amplitude) + 'px, 0); }') styleHtmlAry.push('}') // 容器应用flag-reverse动画 styleHtmlAry.push('#flag {') styleHtmlAry.push('animation-duration: ' + duration + 's;') // 添加周期时长 styleHtmlAry.push('animation-delay: -' + (interval * sliceCountPerPeriod) + 's;') styleHtmlAry.push('}') // 切片样式 styleHtmlAry.push('#flag > li {') styleHtmlAry.push('animation-duration: ' + duration + 's;') // 添加周期时长 styleHtmlAry.push(' ' + (imgWidth / sliceCount) + 'px;') // 设置切片宽度 styleHtmlAry.push('}') styleEle.innerHTML = styleHtmlAry.join('') // 创建切片元素 flagEle.innerHTML = new Array(sliceCount + 1).join('<li></li>') document.documentElement.appendChild(styleEle) } } })(); </script> </body> </html>
效果二:(比较有质感,canvas实现)
代码:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <title>旗帜飘飘</title> <style> * { margin: 0; padding: 0; } html, body { 100%; height: 100%; } body { position: relative; background: lightgrey; } #flagCanvas { position: absolute; top: 50%; left: 50%; transform-origin: center; transform: translate3d(-50%, -50%, 0); } </style> </head> <body> <canvas id="flagCanvas"></canvas> <script> var canvas = document.getElementById('flagCanvas') var ctx = canvas.getContext('2d') var IMG_MAX_WIDTH = 600 var IMG_MAX_HEIGHT = 600 var imgWidth, imgHeight var image = new Image() image.src = 'https://oscimg.oschina.net/oscnet/4073613a5ab7bb799829aa83a67e9f46b7d.jpg' var amplitude = 30 // 振幅 var period = 2 // 周期数 var frequency = 1 // 频率 var wavelength // 波长 var v // 波速 var cftX // x系数 var cftA // 振幅系数 image.onload = function (ev) { imgWidth = Math.floor(image.width) imgHeight = Math.floor(image.height) var canvas = document.getElementById('flagCanvas') var scale = 1 if (imgWidth > IMG_MAX_WIDTH) { scale = IMG_MAX_WIDTH / imgWidth } if (imgHeight > IMG_MAX_HEIGHT) { scale = scale * IMG_MAX_HEIGHT / imgHeight } canvasWidth = imgWidth canvasHeight = imgHeight + amplitude * 2 canvas.width = canvasWidth canvas.height = canvasHeight canvas.style.transform = 'translate3d(-50%,-50%,0) scale(' + scale + ')' wavelength = imgWidth / period cftX = 2 * Math.PI / wavelength cftA = amplitude / imgWidth v = wavelength * frequency tick() } var fps = 70 // 每秒帧数 var interval = 1000 / fps // 连续帧之间间隔(理论) var stop = false // 停止动画 var timeNow = Date.now() // 当前时间 var timeLast = timeNow // 上一帧时间 var delta = 0 // 连续帧之间间隔(实际) var y = 0 var lastY = 0 var distance = 0 var tick = function () { if (stop) return false timeNow = Date.now() delta = timeNow - timeLast if (delta > interval) { timeLast = timeNow distance += (delta / 1000 * v) ctx.clearRect(0, 0, canvasWidth, canvasHeight) for (var x = 0; x < imgWidth; x++) { // var y = cftA * x * Math.sin(cftX * (x - distance)) + amplitude // ctx.drawImage(image, x, 0, 1, imgHeight, x, y, 1, imgHeight) y = cftA * x * Math.sin(cftX * (x - distance)) + amplitude ctx.drawImage(image, x, 0, 1, imgHeight, x, y, 1, imgHeight) ctx.fillStyle = 'rgba(255,255,255,' + (x === 0 ? 0 : (y - lastY) * 0.5) + ')' ctx.fillRect(x, y, 1, imgHeight) lastY = y } } requestAnimationFrame(tick) } </script> </body> </html>