今天要介紹的是每一個 Slider 套件必備的基本功能,雖然是基本功能,但確是困難度最高的主菜,完成了這個功能,其它附加功能都是小菜一碟,慢慢加上去才成了滿漢大餐。
在 Day 17 介紹完 CSS Transform 屬性來改變 Slider 項目區塊的定位,由於沒有動畫效果,看起來似乎沒什麼問題。
圖 a: Day 17 範例網頁畫面
並搭配 Day 18 介紹的 CSS Transition 屬性,為 slider 添加上動畫漸變效果時,很明顯地在視覺上出包了。
圖 b: Day 18 範例網頁畫面
這個例子一共有 6 個項目區塊元素,當按下「下一個」按紐時,第一個項目區塊的位置透過 transform 屬性改變位置。
transform: translateX(500%)
位置從 0% 變成 500%,從第一個變成排在最後一個。
按下「下一個」按紐時,第六個項目區塊透過 transform 屬性改變位置。
transform: translateX(-500%)
位置從 0% 變成 -500%,從最後一個變成排在第一個。
沒有漸變效果時,變化是瞬間完成,因此看不出異狀,但加上漸變效果時,會把 translateX(0%)
變成 translateX(500%)
或 translateX(0)
變成 translateX(-500%)
之間的過程都補上動畫,因此產生從畫面最右邊飛到最左邊、從畫面最左邊放到最右邊的這種情況發生。
和本功能相關的兩個主要檔案:
和實體 HTML 顯示相關的,筆者把它規劃為視圖組件 (view components),和套件功能實作相關的,歸類為核心模組 (core modules)。檔案的分類沒有一個準則,這裡單純是筆者個人喜好。
previousNext.js
顯示「上一個」、「下一個」按紐的視圖組件。
第 20 行:為「上一個」按紐建立一個事件轉接器。
第 21 行:為「下一個」按紐建立一個事件轉接器。
第 26 行:定義「上一個」按紐的點擊事件。
第 30 行:定義「下一個」按紐的點擊事件。
當使用者點擊時,執行 slide
模組。第二個參數為「方向」符號。
slide.js
負責處理 slider 在滑動項目元素的功能模組。
除了往左邊方向、右邊方向,也提供頁籤的方式滑動到指定頁碼,因此處理邏輯的程式碼一多起來,將往「上一個」、「下一個」的邏輯再切分出來,放到 core/modules/slide
目錄底下。
第 26 至 29 行:群組的選項為啟用時,當前的頁碼要改成以整個群組筆數為一頁來計算。
第 31 至 34 行:如果是點擊頁碼,計算當前的位置,判斷該往右邊滑動或左邊滑動。
第 47 至 49 行:當前位置與頁碼相同,就不處理,提前返回 (early return)。
第 51 行:埋入一個內部的事件 slide.start
,可供外部調用。
第 53 行:這裡的 for 迴圈在只有按「上一個」、「下一個」按紐時,*steps* 變數都會只有 1,只迴圈一次,依滑動的方向滑一次。但如果使用者是按頁碼按紐的化,則會計算目前位置需要滑幾次才會到該頁碼位置。
第 70 行:埋入一個內部的事件 slide.end
,可供外部調用。
前情提要提到的尷尬狀況的解決方式,是利用暫時移除 CSS transition 屬性後不會有動畫效果,需要漸變效果時再啟用,狀況分為「往左滑」與「往右滑」,移除的時機也有所不同。
圖 c: 解決方法示意圖 (1)
按下「下一個」按紐時,項目區塊往左滑,在有 transition 效果下,如果直接把第一個項目元素的位置利用 transform 移到最右邊,會看到第一個項目元素暴衝到右邊的動畫。
在邏輯的處理上,先正常往左滑 100% 之後,移除 transition 屬性,再把第一個項目元素移到最右邊即可。
next.js
第 27 至 29 行:先正常地往左滑。
接下來動畫完畢,第 17 行至 23 行都是處理順序號碼為 1 的情況。
其中 queue
函式為 setTimeout 的包裝(以便日後加入其它邏輯一起判斷),在設定的秒數之後,移除 transition 屬性,再把項目元素移到最右邊。
圖 d: 解決問題後畫面 (1)
利用關掉 transition 再換位置,比原本多了一個步驟,運作的很不錯。
以一組為 4 個項目元素,群組方式一起滑動,概念上和移動單個項目元素一樣,只是在順序號碼為 1 時,每一個項目元素都要進行這樣的處理。
第 73 至 75 行:先正常地往左滑。接下來動畫完畢,第 54 行至 69 行都是處理順序號碼為 1 的情況。
圖 e: 解決問題後畫面,群組滑動測試 (1)
測試結果,群組滑動也正常。
圖 f: 解決方法示意圖 (2)
按下「上一個」按紐時,項目區塊往右滑,在有 transition 效果下,第一個項目元素左邊是沒有項目元素顯示,因此必須先把最後一個項目元素的位置調整過來第一個項目元素左邊時,會出現暴衝的情況。
和往左滑的情況相反,我們先移除 transition 屬性,然後往右滑動時再使用 transition 屬性,製造不間斷的循環播放效果。
previous.js
第 17 至 21 行:遇到順序號碼是最後一個的時候,移除 transition 屬性,移動位置。
第 26 至 31 行:等整個文件的 DOM 都 render 之後,再開啟 transition 屬性,進行移動。
setTimeout(() => {
callback();
}, 0)
這邊是使用 setTimeout 第二個屬性值為 0 時,會進入駐列,等待 DOM 都渲染完再執行。這算是一個 trick,日後再想看看什麼方式處理比較安全,所以先定義 queue
函式包裝起來,以後要改就不用改全部有用到的程式碼。
圖 g: 解決問題後畫面 (2)
利用關掉 transition 再換位置,然後再打開 transition 展現動畫效果,運作正常。
以四個項目元素為一組的群組滑動,邏輯單個項目元素滑動的處理方式。
圖 h: 解決問題後畫面,群組滑動測試 (2)
測試正常。
圖 g: 整層移動解決方法示意圖
另一種方式是原本的 Slider 容器不要使用 overflow: hidden
讓全部的項目元素都顯示。
不管寬度有多長,是 1500px 也好,3000px 也好,超過的部分,改由新增加的外層包裝 (wrapper) 進行 overflow: hidden
來隱藏超出畫面範圍的項目元素。
接著平移 Slider,左移或右移都會遇到項目元素到頂部或到尾部的情況。為了達成輪播無限循環,採用這個方法必須複製 (clone) 項目元素到最頂部及最尾部,接著畫面滑動重新定位的方法與前面提到的解決方法相同。
註:雖然寫好了前面的解決方法,後來覺得邏輯有點複雜,為了簡化邏輯,日後的鐵人賽作品 Sliderm.js 會改寫成以平移整個 Slider 區塊作為輪播無限循環的解決方案。
在 Slider 的套件實作中,輪播無限循環又要分為單個項目個別滑動及群組滑動,邏輯上頗為複雜。遇到這種需要計算相對位置,條件又多時,可以拿出紙筆先畫草稿,或寫在白板上,標註一下各個情況下,各個項目元素的平移距離,對於程式設計上也會有幫助。
文中範例可在 GitHub Page 閱讀。
原始碼可在 2022 鐵人賽專用 GitHub Repo 下載。
本次鐵人賽的 Sliderm 套件初版在,demo/sliderm-alpha,不會進行修改,後續的功能開發可以瀏覽 Sliderm.js 官方網站。