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