hello,民娜桑~~我又來開新坑了( ̄ε(# ̄)╰╮o( ̄皿 ̄///),這次盡量保證把這個坑填完~
本系列我會分四篇來完成主題,分别是① DIV+CSS的實作,② canvas2D的簡單實作,③ canvas2D的進階實作,④ webgl+着色器的實作 以及 ⑤ 包裝成jquery插件并釋出為npm子產品 。
這是整個系列完成以後的最終效果:

開始閱讀之前請確定您對高中的三角函數還有一定的印象以及了解基本的canvas繪圖操作——當然如果你确實不了解也沒事,這篇文章是使用div和css的實作,暫時沒有用到以上的知識。
首先講一下實作的原理,拿到一張圖檔後,擷取其寬度,然後在性能允許的情況下,切成盡可能細的豎直切片,每個切片都用同一張背景圖檔并将背景圖檔的位置移動到切片的對應位置,然後通過css3關鍵幀動畫使切片元素以不同的時間軸來進行上下移動。很簡單是吧,如果你覺得so easy或者想根據原理自己試着實作一遍,那本文的後面你就可以直接跳過了。
html結構很簡單:
<!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%;
width: 100%;
background-color: lightgrey;
}
body {
text-align: center;
position: relative;
}
ul, li {
list-style: none;
}
#flag {
position: absolute;
left: 50%;
top: 50%;
}
/* 這裡是核心css樣式 */
</style>
</head>
<body>
<ul id="flag"></ul>
<script>
(function () {
// 這裡是js代碼
})();
</script>
</body>
</html>
然後,準備一張圖檔,比如這張艹貓路飛團的海盜旗,哎呀手滑,是草帽路飛團 (๑•̀ㅂ•́)و
接下來添加核心css代碼:
/* 這裡是核心css樣式 */
#flag > li {
height: 100%;
float: left;
background-image: url("./img/flag.jpg");
background-size: auto 100%;
animation: flag ease-in-out infinite;
}
是的,你沒看錯,就是這麼點~~事實上并非如此,為了實作代碼的靈活性,比如自定義周期數、周期長度、振幅、切片數量等,我使用js代碼動态建立了style标簽,并将屬性計算後寫入。
下面是js代碼,圖檔位址我暫時是寫死的,通過上傳圖檔自動生成動畫我會在最後一節封裝插件時作為補充來說明。
// 這裡是js代碼
var flagEle = document.getElementById('flag')
var image = new Image()
image.src = './img/flag.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, 2, 2)
}
雖然在圖檔加載後有一堆代碼,但是除了 splitImg(100, 20, 2, 2) ,事實上其他都無關緊要,前面那段代碼的主要作用是定義一個容器的最大寬高,如果超過将會被等比例縮放。(不過并不推薦使用大圖,性能會是一個大問題)
下面使這段程式的核心方法——splitImg:
/**
* 分割圖檔
* @param sliceCount 切片數量
* @param amplitude 振幅
* @param period 固定周期個數
* @param duration 一個周期的時長
*/
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('#flag > li {')
styleHtmlAry.push('animation-duration: ' + duration + 's;') // 添加周期時長
styleHtmlAry.push('width: ' + (imgWidth / sliceCount) + 'px;') // 設定切片寬度
styleHtmlAry.push('}')
styleEle.innerHTML = styleHtmlAry.join('')
// 建立切片元素
flagEle.innerHTML = new Array(sliceCount + 1).join('<li></li>')
document.documentElement.appendChild(styleEle)
}
① 這裡的波形圖是使用的cos函數的表示形式,添加了三個關鍵幀:從波峰到波谷,再回到波峰。
② 因為使用了ease-in-out的動畫曲線,是以可以模拟出三角函數的波形圖
③ 原理和代碼都比較簡單,可能比較需要注意的是這句 styleHtmlAry.push('#flag > li:nth-child(' + formula + i + ') { '),對css3了解的朋友應該知道:nth-child的用法,括号裡面的是一個等差數清單達式,項數規定用n表示,那麼公差是多少呢,由于我們的動畫是周期性的,是以公差應該是每個周期包含的切片數量(正整數),即 var sliceCountPerPeriod = Math.floor(sliceCount / period)。
寫完以上代碼,我們的基本雛形就出來了,這是切片數為80份,振幅20機關,2個周期,周期時長為2秒 時的效果圖:
是不是看着有點不對勁?旗子不是應該一邊固定的麼?怎麼兩邊一起動了?
辦法也簡單,隻要我們在容器上加一個反方向的運動不就好了?
修改#flag樣式,添加如下樣式:animation: flag-reverse ease-in-out infinite;
#flag {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%,-50%,0);
animation: flag-reverse ease-in-out infinite;
}
如下位置添加js代碼:
// 添加關鍵幀動畫
...
// 添加反向關鍵幀動畫
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('}')
// 切片樣式
...
似乎沒問題了,看看效果:
納尼?怎麼兩邊都固定了?原來是因為我們指定2個周期,隻要不是周期的整數倍就行了,在原來的基礎上改為1.5個周期試試:
到這裡我們的dom+css的實作方式就結束啦,這種方式的優點很明顯,就是實作簡單;缺點也不少,比如無法添加高光效果,整體振幅一緻不符合常理,切片過多容易造成的頁面阻塞與記憶體洩露,下一節 我會用canvas2D像素級的操作實作該效果,可以很大程度上避免這些問題。
Demo:See the Pen
flag waving by dom+cssby Kay (
@oj8kay) on
CodePen.
原文釋出時間為:2018年07月03日
原文作者:
oj8kay本文來源:
開源中國如需轉載請聯系原作者