vue.js實現仿原生ios時間選擇組件
前言
最近幾個月一直在看VUE,然后試著只用原生js+vue實現某些組件。
PC端時間選擇組件 這是最開始實現的pc上的時間選擇,平時移動端也在做,所以就想實現一下移動端的時間選擇器,下面分享一下我實現移動端滾輪特效時間選擇器的思路和過程。整個組件是基于vue-cli來進行構建的
實現過程有點多,大家有沒懂的地方請留言,我會做對應調整
功能
1.時間選擇[ A.年月日選擇 B.年月日小時分鐘選擇 C.小時分鐘選擇 D.分鐘選擇]
2.滾輪效果[ A.構成一個圓環首尾相連 B.不構成首尾相連]
3.時間選擇范圍設置(所選時間超過范圍將彈窗提示),分鐘間隔設置
4.多語言設置
5.時間格式設置 滿足 yyyy/MM/dd HH:mm 這一類的設置規則
6.UE上做到接近ios原生效果
7.擴展 不僅僅只能選擇時間,可以傳入自定義聯動選擇數據
這里主要講講無限滾輪的實現
數據準備1
這里拿 天 來做說明
獲取一個月有多少天的一個巧妙的方法,
dayList () {
/* get currentMonthLenght */
let currentMonthLength = new Date(this.tmpYear, this.tmpMonth + 1, 0).getDate();
/* get currentMonth day */
let daylist = Array.from({length: currentMonthLength}, (value, index) => {
return index + 1
});
return daylist
},
這里我用了vue 的computed方法來實現,放入 yearList monthList dayList hourList minuteList 來存儲基礎數據,這里數據準備就先告一段落。
靜態效果實現
實現滾輪靜態效果有多種方式
1.視覺3D效果[加陰影]
2.實際3D效果[CSS3D]
我把實現效果大致分為上面2種,具體的大家可以自己搜索相關資料,這里展開涉及太多就帶過好了
我自己實現是用的第二種采用了CSS3D
說明
首先我們看到原生ios的選擇效果在進入選擇范圍內和選擇范圍外的滾輪是有差別的
所以為了實現這個效果差別我選擇用2個dom結構來實現,一個dom實現滾輪,一個dom實現黑色選中效果,這樣聯動的時候就有類似原生的效果差別
picker-panel 裝各種選擇dom,這里只給出了day的, box-day 裝天數據的一個最外層盒子, check-line 實現選中的那2條線, day-list 最外層黑色效果數據, day-wheel 灰色滾輪部分
<div class="picker-panel">
<!--other box-->
<div class="box-day">
<div class="check-line"></div>
<div class="day-checked">
<div class="day-list">
<div class="list-div" v-for="day in renderListDay">
{{day.value}}
</div>
</div>
</div>
<div class="day-wheel">
<div class="wheel-div" v-for="day in renderListDay" transform: rotate3d(1, 0, 0, 80deg) translate3d(0px, 0px, 2.5rem);>
{{day.value}}
</div>
</div>
</div>
<!--other box-->
</div>
.day-wheel{
position: absolute;
overflow: visible;
height: px2rem(68px);
font-size: px2rem(36px);
top:px2rem(180px);
left: 0;
right: 0;
color:$unchecked-date;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
.wheel-div{
height: px2rem(68px);
line-height: px2rem(68px);
position: absolute;
top:0;
width: 100%;
text-align: center;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
主要涉及的css屬性
transform-style: preserve-3d;
展示3D效果,
-webkit-backface-visibility: hidden;
滾輪背后部分自動隱藏
postition:absolute;
用來定位輪子
transform: rotate3d(1, 0, 0, 80deg) translate3d(0px, 0px, 2.5rem);
每個數據旋轉的角度 和滾輪側視圖圓的半徑
每個數據旋轉的角度和構造原理
如上圖
是我們滾輪的效果立體圖,r 就是我們 translated3d(0px,0px,2.5rem) 這條css中的2.5rem,
如果沒有這句css 那么所有的數據將匯聚在圓心
上圖 不做旋轉(紅色代表我們看到的數據效果)
上圖 做了旋轉(紅色 橙色代表我們看到的數據效果)
藍色弧線表示的角度是一樣的(這個涉及角的知識),也是視覺旋轉角度,就是rotate3d這句css里面的80deg ,我做的是每個間隔20度,這樣實際我們只用旋轉x軸就順帶旋轉了圓心角度,這樣就把整個環給鋪開了。
完整一個圓可以裝下360/20 個數據,而我們肉眼正能看見正面的數據,所以過了一定角度就在背后應該不能被我們看見,而-webkit-backface-visibility: hidden;這句話就起了作用。
這里我們發現輪子裝不完所有數據,而且我們要實現數據循環
類似下圖效果
所以就有了第二次數據準備
數據準備2
這里也是用我們的dayList作為初始數據[1,2,3,4,.....,30,31]
這里我們每次取19個數據來作為渲染數據,而我們需要renderListDay初始呈現是[23,24,25,26,27,28,29,30,31,1,2,3,4,5,6,7,8,9,10]
因為這樣取最中間的數剛好是第一個(僅在初始化的時候)
renderListDay(){
let list = [];
for (let k = this.spin.day.head; k <= this.spin.day.last; k++) {
let obj = {
value: this.getData(k, 'day'),
index: k,
};
list.push(obj)
}
return list
},
取數據的方法 小于0倒著取 大于0正著取,索引大于原始數據長度都用%計算來獲得正常范圍對應的索引,
所以上面的spin 就是我們的取數據的叉子(初始是從-9到9)
getData(idx, type){
//...
else if (type == 'day') {
return this.dayList[idx % this.dayList.length >= 0 ? idx % this.dayList.length : idx % this.dayList.length + this.dayList.length];
}
//...
},
每條數據旋轉的角度(上半圓是正,下半圓是負)
<div class="wheel-div" v-for="day in renderListDay" v-bind:data-index="day.index" v-bind:style="{transform: 'rotate3d(1, 0, 0, '+ (-day.index)*20%360+'deg) translate3d(0px, 0px, 2.5rem)'}">{{day.value}}{{day.value}}</div>
這里需要仔細理解,如有沒說清楚的地方,請留言指出謝謝
接著需要旋轉到我們需要的角度,跟我們的初始化時間對上,this.orDay-this.DayList[0] 是獲取偏移量來矯正角度
this.$el.getElementsByClassName('day-wheel')[0].style.transform = 'rotate3d(1, 0, 0, ' + (this.orDay - this.dayList[0]) * 20 + 'deg)';
增加touch事件
剩下的事就很好處理了,給對應的dom綁定事件根據touchmove的距離來轉換成旋轉的角度 和check-list的位移
這里translateY是用來記錄實際移動的距離的,最后輸出需要算入偏移量
<div class="box-day" v-on:touchstart="myTouch($event,'day')" v-on:touchmove="myMove($event,'day')" v-on:touchend="myEnd($event,'day')">
<div class="check-line"></div>
<div class="day-checked">
<div class="day-list" data-translateY="0" style="transform: translateY(0rem)">
<div class="list-div" v-for="day in renderListDay" v-bind:data-index="day.index">
{{day.value}}
</div>
</div>
</div>
<div class="day-wheel" style=" transform: rotate3d(1, 0, 0,0deg)">
<div class="wheel-div" v-for="day in renderListDay" v-bind:data-index="day.index" v-bind:style="{transform: 'rotate3d(1, 0, 0, '+ (-day.index)*20%360+'deg) translate3d(0px, 0px, 2.5rem)'}">
{{day.value}}
</div>
</div>
</div>
慣性滾動
這個實現我是用了一個 cubic-bezier(0.19, 1, 0.22, 1)
判斷手勢是不是flicker 如果是flicker通過一個瞬時速度來算出位移,和時間,然后一次性設置,然后用transition做慣性滾動,
普通拖動 設置1秒
這個實際效果還是有點不好,以后來改進。
其他功能的實現
這里不做詳細說明了
總結
自適應方面用了手淘的解決方案
這次實現這個組件最困難的就是實現無限滾動,和無限滾動的渲染數據的構造,接著就是慣性滾動的實現。
已知問題
1.慣性滾動不完美
2.無限滾動實現了。非無限滾動沒實現,就是渲染數據就是[1,2,3,4,5,6,7,8,9,10] (懶)
3.現在選擇必須 年月日 或者年月日小時分鐘 不能單獨選小時或者分鐘
這篇文章會不定時修改完善,因為實現無限那個有點繞,我在組織組織語言 -_-~
來自:https://segmentfault.com/a/1190000007863240