之前放假在家的時候,群裡有一個朋友問我,有沒有無縫輪播的思路,百度了一下,原來無縫輪播指的是傳統輪播圖中最後一張輪播圖下一張是第一張輪播圖,不會穿過中間的輪播圖。
給個例子吧
同時放上codepen的地址
https://codepen.io/shadowwalkerzero/pen/XqeZjQ
傳統的思路
這是原生js實現的無縫輪播效果,給大家簡單講一下思路,以下一張為例
流程是:
假設六張圖片分別為 A B C D E F
點選下一張button
所有圖片左移 A 圖片的寬度
圖片變為 B C D E F A
複製程式碼
然後我們拿程式碼來描述一下
var img0 = document.querySelector('img')[0];
var img0Width = img0.offsetWidth,
var imgContainer = document.querySelector('.box'); //裝載圖片的容器
imgContainer.animate({
marginLeft: -img0Width
}, function(){ // 動畫結束回撥方法
imgContainer.style.marginLeft = 0; //將marginleft 回覆到0
var newNode = img0.cloneNode(true); // 替換順序
imgContainer.removeChild(img0);
imgContainer.appendChild(img0);
})
複製程式碼
其實思路是很簡單的,但是仔細一想這裡有個不好的點,就是這句程式碼。
imgContainer.style.marginLeft = 0;
複製程式碼
為什麼說這句程式碼不好呢?這裡是利用計算機強大的cpu,讓我們可以在肉眼不可見的速度迅速右移,我們來詳細模擬一下
下一張:
假設六張圖片分別為 A B C D E F
左移動畫(緩動)
A B C D E F
動畫回撥(瞬時)
B C D E F A
複製程式碼
大家有沒有發現這樣的動畫非常不符合規律
- 我們移動了裝載圖片的容器造成左移,但是最後又把它瞬間右移, 不符合圖片移動的語義化。
react的思路
因為我們可以明確圖片的起始狀態
A B C D E => B C D E A
圖片的移動描述
(B C D E) 左移
(A) 右移
複製程式碼
也就是說我們可以明確圖片的動畫前後狀態,而圖片的順序就是圖片的移動距離。這非常符合react的設計哲學啊,好,我們拿react再寫一版。 這裡我們用原生js來模擬一下react,方便大家加深一下react的學習。
1.設定初始的state
const defaultState = Array(items.length).fill(0).map((item, index) => {
return {
key: `item${index}`,
style: {
left: (index + currentNum) * 100,
opacity: 1
}
}
})
複製程式碼
這裡items是img 標籤的nodelist,currentNum 是表示圖片初始的偏移量(-1 表示所有圖片左移1張,1表示所有圖片右移一張)
2.順序改變後,更新state
const getState = (states, moveItemKey) => states.map((state, index) => {
return {
key: state.key,
style: {
left: (index + currentNum) * 100,
opacity: moveItemKey === state.key ? 0 : 1
}
}
})
const setState = (newStates, moveItemKey) => {
return getState(newStates, moveItemKey);
}
複製程式碼
這裡moveItemKey 是需要在 A B C D E => B C D E A 中 的 A 圖片。這裡有一個問題,當A圖片從第一張移動到最後一張時,A會穿過 B C D E, 這樣肯定是不行的,所以我們提前拿到了A的key,把A的透明度改變成了0,這樣即使穿過 B C D E,使用者也無法發覺。
3.給 DOM 元素繫結key值
const setAttr = () => [...items].map((item, index) => {
item.setAttribute('key', states[index]['key']);
});
const render = () => {
[...items].map((item, index) => {
var key = item.getAttribute('key');
states.map((state, i) => {
if (state.key === key) {
item.style.left = state['style']['left'] + 'px';
item.style.opacity = state['style']['opacity'];
}
});
})
}
複製程式碼
這裡我們把對元素的key值 綁在了dom的屬性上,render方法則會依據state的資料渲染dom。
4.新增上一頁,下一頁事件
const prev = () => {
var moveItem = states.slice(states.length - 1);
newStates = [...moveItem, ...states.slice(0, states.length - 1)];
states = setState(newStates, moveItem[0].key);
render();
}
const next = () => {
var moveItem = states.slice(0, 1);
newStates = [...states.slice(1), ...moveItem];
states = setState(newStates, moveItem[0].key);
render();
}
prevButton.onclick = prev;
nextButton.onclick = next;
複製程式碼
以下一頁事件(next) 為例,我們只需要把圖片組中,第一張圖片移至最後就可以了。這裡有一點遺憾的是,沒實現state變化 自動render的部分,不過這裡主要是講react的動畫思路。
最後看看拿react思路改寫後的效果吧
同時放上codepen的地址
https://codepen.io/shadowwalkerzero/pen/rvGvjJ
相比傳統的無縫輪播,拿rect的思路改寫後,程式碼可讀性變的更高,思路更加清晰,同時程式碼也變得更少,更容易維護。
官方的react-motion
當然最終的react動畫方案必須是react-motion了,motion提供了各種動畫引數,動畫做的異常逼真,自己也按照react-motion的API,實現了一版react-motion的無縫輪播,效果如下。
因為引入了react-motion,動畫成本大大降低,編寫的程式碼也十分少。這裡留個地址吧,感興趣的同學可以去看看。
一點總結
現在來看動畫,覺得動畫都是漸進,有規律的,通過操作元素從一個位置瞬間移動到另一個位置,個人覺得是違背了動畫的理念的,因為從一個位置瞬時的改變到另一個點,這樣是無法描述(或者說不符合動畫的語義化),可以參照我們之前用傳統方法實現的輪播。 現在我們來寫動畫,我們應該先把動畫的狀態的描述出來,然後描述把動畫的起始態和結束態,這樣的動畫才是規律的。