摘要:主要是為了阻止微信瀏覽器的默認滑動。四如何利用五個做到無限滑動其實我在寫第一個版本的日歷的時候,采取的解決辦法是當新的月份產生之后,往中不斷。如何控制的值實現滑動效果,這個問題不是這次的重點。
之前寫了一篇Calendar -『為移動端而生』的自定義日歷,一直有童鞋對這個插件的手勢處理存在一些問題,所以想寫篇文章,來說說它的成長史~
在閱讀本文之前,確保你有稍微看過 calendar 的效果 喔~
點擊查看github, 查看calendar源碼
也可以在 NPM上搜索 mob-calendar 找到它。
一、 確認需求想做一個日歷最主要的原因,當然還是因為在開發過程中頻繁的遇到。而且對日歷的需求又是奇葩到不行,市面上的插件都滿足不了我們產品的需求。所以,我不得不動手自己造。
這段話,好像在造 上一個插件 - 級聯選擇器 的時候也說過
大家就當無事發生過(?????????)
首要問題依然是處理需求:
第1個問題:『日歷的出現場景有哪些特點?』用戶不確定自己要選擇的時間點或時間范圍,需要一些基本的時間參照單位,比如“下星期一”、“下個周末”。
用戶需要查看某個時間區間,之后再有選擇性的選取時間點或時間范圍,比如“盡可能避開周末的20天翹班請假計劃”。
用戶需要查看某個時間區間的行為記錄,比如“查看過去幾周的打卡情況”
當出現以上問題的時候,日歷的時間定位優勢就顯示出來了。
第2個問題:『日歷會有哪些奇葩需求?』日歷存在著點擊事件,點擊事件是 跳轉事件 還是 高亮事件 無法預知。
日歷存在著選取操作,選取的結果是 時間點 還是 時間范圍 無法預知。
日歷有多種展現形式,是直接 文檔流顯示 還是 彈層顯示 無法預知。
針對這些不穩定因素,接下來,會帶你一步步解決。
二、構造函數的參數設計確定了日歷的需求,就來設計一下構造函數的參數吧~
第3個問題:『日歷有哪些常見的展現形式?』從現在市面上的常見的app上看,我們會發現,日歷常見的展現形式有兩種:
普通文檔流形式
彈層形式
在參數的設置中,表現為設置isMask,false:普通形式,true:彈層形式。
?
1. 讓開發人員更方便地定位日期
①:在確定時間范圍的時候,使用一個 length 為 3 的數組,數組的每一位分別對應【年】【月】【日】
比如beginTime、endTime 和 recentTime的設定②:在對特定日期指定樣式或操作的時候,使用該日期的時間戳。
比如設置beforeRenderArr的時候,需要傳入一個符合規范的對象數組
參數 | 類型 | 舉例 | 說明 |
---|---|---|---|
stamp | {Number} | eg:1514822400000 | 指定一個特定的時間戳 |
className | {String} | eg: "enable" | 指定一個用戶自己設置的css的類名 |
2. 靈活控制星期的排列、星期的顯示格式、月份的顯示格式
①:isSundayFirst 控制星期日是否要放在第一列,true為星期日放第一列
②:isChinese 控制星期的顯示方式,true為顯示中文,false為顯示英文
③:monthType 控制月份的顯示格式,以一月份為例,0: 1月, 1: 一月, 2:Jan, 3: January
3. 對最重要的滑動手勢做一些配置
①:angle 控制滑動的角度,間接控制靈敏度,建議取值范圍5-20
②:isToggleBtn 是否需要展示切換按鈕, true為需要展示
③:canViewDisabled 是否可以查詢不在規定范圍內的月份,true為可以查詢
4. 可供開發者自定義的靈活的回調函數
三、暴露在原型上的、可使用的api①:success 點擊某個日期之后的回調,用戶自定義點擊后的操作。自帶參數(item, arr)。item為當前點擊的時間戳,arr為智能判斷后的連續兩次點擊的兩個時間戳的數組
②:switchRender 切換月份時的回調,用戶自定義切換后需要進行的操作,如發起請求更新數據等。自帶參數(year, month, cal)。year為新生成的年份,month為新生成的月份(從0開始), cal指向當前實例
名稱 | 傳入參數的類型 | 作用 |
---|---|---|
renderCallbackArr(arr) | {Array} | 渲染指定的arr,arr的格式和beforeRenderArr的對象數組的格式一樣 |
prevent() | - | 在微信瀏覽器中,你可能需要用到的阻止默認事件的api |
hideBackground() | - | 在彈層模式的success回調中,你可能需要用到的關閉彈層的api |
適當解釋一下api的用意:
1.向renderCallbackArr中傳入一個數組,(數組格式和beforeRenderArr一樣,不再說明),這個方法能夠往你需要的時間點上添加指定樣式。設想一種場景:
通過滑動切換,查看三個月前的打卡情況,已打卡和未打卡的日期都有不同的高亮樣式。
顯然,這個月的打卡情況是需要你在switchRender回調中發起http請求后得到。
在http返回結果后,構造一個符合beforeRenderArr格式的數組,然后調用renderCallbackArr,傳入構造好的數組,就能對指定的日期渲染指定的className了。
// 舉個栗子? switchRender: function(year, month,cal) { console.log("計算機識別的: 年份: " + year + " 月份: " + month); $.ajax({ url: "xxxx", type: "GET", data: { applyYear: year, applyMonth: (month + 1), }, success: function(newArr) { cal.renderCallbackArr(newArr); } }) }
2. 使用prevent()的場景應該不會太多。主要是為了阻止微信瀏覽器的默認滑動。
// 這是prevent 方法的源碼 prevent: function (e) { e.preventDefault(); },
3.使用hideBackground()的場景一般是在彈層模式的success回調中。設想一種場景:
四、如何利用五個DOM做到無限滑動觸發了日歷彈層之后,如果你只想【選擇一個時間點】,那么點擊某個日期之后就可以直接調用hideBackground()收起彈層。
如果你想【選擇某個時間區間】,那么可以在第二個時間點確定之后再調用hideBackground()收起彈層。當然,也可以不收起彈層。
其實我在寫第一個版本的日歷的時候,采取的解決辦法是當新的月份產生之后,往body中不斷append dom。不過當時的業務的場景比較簡單,撐死也只有10個月。但是顯然如果有100個月,我這樣的做法明顯不行。
所以必須要讓dom可以復用,實現無限滑動
思考第5個問題:『無限滑動的話至少需要幾個dom呢?』首先明確,這里指的一個dom就是一個月份,每次切換月份就是切換包裹著月份的dom
如下圖,假設當前月份為【2017年9月】,由于滑動是實時的,當我的手指從右向左滑的過程,【2017年10月】也會漸漸的露出來一些,考慮一種特殊情況:
以打卡為例,2017年10月是有打卡記錄的,如果等使用者松開手指,停在2017年10月的時候突然閃現出打卡記錄的高亮樣式,會給使用者很不舒適的感覺。
為避免這種情況,就需要在當前月份為【2017年9月】的時候,就已經渲染好【2017年10月】的高亮樣式了,左邊的【2017年8月】也是同理,所以至少必須要渲染出完整的、帶有數據高亮的三個月
所以我們得到了結論,月份的dom至少為3個,并且這三個dom是已經連高亮樣式都渲染好,不會在實時滑動結束后有任何變動的。
但是為什么最后是要用5個dom來實現無限滑動呢?
參考一下swiper的效果,為了能讓這三個dom兩邊的極端dom也能夠正常的實時滑動。所以在頭尾分別加一個dom,所以一共需要5個dom來實現無限滑動。
如下圖,綠色線框的部分為最初開始分析的3個dom。
?
直接參考一下swiper的效果就能夠得到答案,我現在舉一個實例來做一些說明:
先考慮以下情況:
手勢操作:連續從右向左滑
操作結果:連續查看下個月
以下是圖例,紅色箭頭的更新操作:
?
以當前進入頁面的初始月份是2017年9月為例:
初始狀態:
?
?
紫色的數字是代表月份dom的下標,相同下標對應的月份也相同。
中間的1、2、3對應的是之前說過的 -----【至少要提前渲染好3個月份的dom】。
那首尾填充的月份為什么是 3 和 1 呢?
假設我們現在不限制5個dom,而是無限個dom,那么代表月份dom的下標組合就會是:
1、2、3、1、2、3、1、2、3、1、2、3......
我們以一個1、2、3為中心,取到連續的5個月份dom,那么取到的下標組合就是:
1、2、【3、1、2、3、1】、2、3、1、2、3......
沒懂沒關系,看下去就會明白。
思考第7個問題:『為了配合無限滑動,要怎么控制顯示的月份呢?』實際上,未來,我會需要取到dom的下標進行更新月份數據的操作,所以我試圖發現【3、1、2、3、1】這個下標數組中的規律。
我發現這個下標循環是3的循環,我可以通過取3的模的方式取到每個位置上的dom下標。
現在我要對這個下標做一點小的改動。
我要把3改成0。即【0、1、2、0、1】
原因很簡單,是為了在計算滑動距離的時候,將 dom下標 和 translateX 對應起來比較方便。即當滑到最左側的月份dom的時候,月份的dom的translateX的值為0,可以和下標 0 % 3 的結果相對應。
這樣,這個下標,就和translateX直接聯系起來了。
好,以初始月份是2017年9月為例,最終初始化的結果為:
?
?
接下來,從右向左滑,查看下一個月份,touchend之后,操作如下:
??
當滑到了最右邊的月份的dom的時候(其實只要滑到邊界都做一樣的處理),在touchstart中執行一個特殊操作:
就是在touchstart的時候,瞬間translate3d到和它dom下標一樣的月份去:
比如上面【2017.11】已經到最右邊的,那在我下次滑動的touchstart的時候定位到下圖的位置中:
?
??
這就是實現無限滑動的核心原理。當然還可以接著一直滑:
??
?
以此類推,無限左滑也是類似的道理。
從上面講述無限滑動的原理中,你可以大概感覺到: 滑動的距離是通過控制中間的灰色矩形相對于手機屏幕的translateX來決定的。
如何控制translateX的值實現滑動效果,這個問題不是這次的重點。
重點是,思考第8個問題:『如果只在日歷的dom區域中控制translateX,當我想滑動整個頁面的時候,滑不動,怎么辦?』假設下圖中的藍色曲線代表用戶的滑動曲線:
當用戶的滑動曲線是A的情況時,用戶的意圖明顯是想把頁面往上拉
當用戶的滑動曲線是B的情況時,用戶的意圖明顯是想查看上一個月
可實際上,如果只通過控制translateX的值實現滑動效果的時候,無論是曲線A或者B都會被認為是想查看上一個月
???
也就是說,如果控制了translateX,那么,在這個占據著文檔流巨大的面積的dom范圍內,永遠無法上下滑動。這是萬萬不被允許的。
所以我們需要預判手勢,來實現在日歷的dom范圍內,既能夠上下滑動,又能夠左右滑動。效果如下:
比如之前提到的【滑動曲線A和B】的示例圖,如果以綠線為標準,
斜率小于綠線的曲線,都歸為和滑動曲線B一樣的左右滑動
斜率大于綠線的曲線,都歸為和滑動曲線A一樣的上下滑動
這樣不就可以了嗎?
但其實用戶的手勢曲線一般都是下面的橙色曲線....
????
而且計算用戶手勢的斜率一定是在touchmove中實時計算(為什么?當然是為了實時滑動),所以最后,靠斜率預判用戶手勢的思路,就到這里結束了。
六、如何利用微積分預判用戶手勢 思考第10個問題:『對用戶手勢進行積分,就能夠解決問題嗎?』用戶的手勢實際上是一條弧線,當前只考慮從左下角向右上角滑的情況,就能把用戶的手勢曲線簡化在第一象限中。
如下圖,我們從微積分的概念出發,得到以下結論。
?????
先看看中間的紅色矩形部分,這個紅色矩形是把某個細長條矩形夸張的放大后的矩形,其寬為△X,其高為△Y。
通過touchmove實時計算每一次滑動的△X 和 △Y,然后累加面積。面積的累加實際上直接按照△X × △Y的結果正負進行累加,這樣就把第一象限的手勢推廣到所有象限的手勢中去了。
計算手勢的核心代碼如下,其中cal指向當前實例:
?
?????
我們可以利用用戶手勢的曲線面積來把用戶手勢操作量化。
但量化是量化了,要如何知道我量化的結果是上下滑動還是左右滑動呢?
所以就需要像計算斜率時的標準線(那條綠線)一樣,必須有一個標準面積。
如下圖,我們有三條曲線,這三條曲線與X軸圍起來的面積,就是我們前面辛辛苦苦量化的結果。其中:
藍色的曲線圍成的面積就是我們理想中的標準面積,雖然還不知道怎么算
黃色的曲線圍成的面積比標準面積大,我們將判定所有大于藍色曲線的量化曲線為【用戶試圖上下滑動】
綠色的曲線圍成的面積比標準面積小,我們將判定所有小于藍色曲線的量化曲線為【用戶試圖左右滑動】
???
問題回到了,如何計算標準面積?
觀察上圖可以發現有一個明顯的藍色的角A,這個角A和實例化的參數angle是同一個東西。
開發者可以通過控制angle的值(angle的單位是°)來控制標準面積的大小。
當然通過我的測試,angle的取值在 [5 , 20]最佳。
那源碼中是如何通過開發者傳入的angle進行標準面積的計算的呢?
首先,我會將用戶的角度轉化為tan值。
?
???
為什么需要tan值呢,因為我就可以根據△X 計算得到 △Y = △X * tanA。
?
???
所以標準面積也能通過累加得到了。
?
???
至此,我們就可以通過用戶手勢的面積和標準面積的比較來得到一個比較理想的預判。
通過預判,讓用戶在頁面的任何地方滑動,都感到舒適。
Github地址:『為移動端而生』的自定義日歷插件 https://github.com/AppianZ/calendar
歡迎大家提出寶貴建議和技術交流 ?(????????)
我是嘉寶Appian,一個賣萌出家的算法妹紙(??????)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/50944.html
摘要:主要是為了阻止微信瀏覽器的默認滑動。四如何利用五個做到無限滑動其實我在寫第一個版本的日歷的時候,采取的解決辦法是當新的月份產生之后,往中不斷。如何控制的值實現滑動效果,這個問題不是這次的重點。 之前寫了一篇Calendar -『為移動端而生』的自定義日歷,一直有童鞋對這個插件的手勢處理存在一些問題,所以想寫篇文章,來說說它的成長史~ 在閱讀本文之前,確保你有稍微看過 calendar ...
摘要:主要是為了阻止微信瀏覽器的默認滑動。四如何利用五個做到無限滑動其實我在寫第一個版本的日歷的時候,采取的解決辦法是當新的月份產生之后,往中不斷。如何控制的值實現滑動效果,這個問題不是這次的重點。 之前寫了一篇Calendar -『為移動端而生』的自定義日歷,一直有童鞋對這個插件的手勢處理存在一些問題,所以想寫篇文章,來說說它的成長史~ 在閱讀本文之前,確保你有稍微看過 calendar ...
摘要:,歡迎使用中文文檔在后面自我介紹是為了滿足移動端對各種場景的需求而生的,兼容性強,靈活度高。如空數組默認設置成當月日數組的每一位分別是年月日。 Calendar - A Flexible Calendar for Mobile Intro Calendar was born for several product requirements in the mobile. It’s f...
摘要:,歡迎使用中文文檔在后面自我介紹是為了滿足移動端對各種場景的需求而生的,兼容性強,靈活度高。如空數組默認設置成當月日數組的每一位分別是年月日。 Calendar - A Flexible Calendar for Mobile Intro Calendar was born for several product requirements in the mobile. It’s f...
閱讀 1049·2021-11-24 09:39
閱讀 3578·2021-11-22 13:54
閱讀 2542·2021-10-11 10:59
閱讀 773·2021-09-02 15:40
閱讀 1025·2019-08-30 15:55
閱讀 1042·2019-08-30 13:57
閱讀 2305·2019-08-30 13:17
閱讀 3025·2019-08-29 18:32