大家好,由于最近從事的是微信公衆号和APP内嵌 H5開發,避免不了開發一些和native相同的操作功能,就如接下來說的 仿IOS滾輪選擇器。github源碼連結 https://github.com/zhangKunUserGit/vue-component大家可以下載下傳運作
先來個截圖:
先來屢一下需求:
1.移動端使用者手上下滑動,内容上下移動,使用者手離開數字按照慣性移動一段距離。
2.當停止移動後,選中一個文字并且文字高亮,上面的值會變成你選中的文字。
3.可以連續滾動。
說起滾動,不得不提css3的 transform-style: preserve-3d; 和 backface-visibility: hidden;
(1)transform-style 屬性規定如何在 3D 空間中呈現被嵌套的元素。值如下圖:
我們使用preserve-3d 是讓我們的值清單呈現3d效果,他是寫在清單父級;
(2)backface-visivility 屬性定義當元素不面向螢幕時是否可見。
我們使用hidden是背面不可見的,他是寫在清單上
不過隻有他們是無法完成這個艱巨界面的。隻是這兩個比較少見并少用,在此記錄一下。
結合上面的知識點那我們怎麼實作滾筒呢?
我實作的方法如下:(transform-style為什麼子元素需要定位?)
我實作的方法如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
padding: 0;
margin: 0;
list-style: none;
}
.wrapper{
margin: 200px auto;
width: 200px;
position: relative;
}
ul{
width: 100%;
transform-style: preserve-3d;
position: absolute;
top: 40%;
left:0;
height: 34px;
}
li{
backface-visibility: hidden;
position: absolute;
left: 0;
top: 0;
height: 34px;
text-align: center;
width: 100%;
}
</style>
</head>
<body>
<div class="wrapper">
<ul>
<li style="transform: rotate3d(1, 0, 0, 80deg) translate3d(0, 0, 100px)">27</li>
<li style="transform: rotate3d(1, 0, 0, 60deg) translate3d(0, 0, 100px)">28</li>
<li style="transform: rotate3d(1, 0, 0, 40deg) translate3d(0, 0, 100px)">29</li>
<li style="transform: rotate3d(1, 0, 0, 20deg) translate3d(0, 0, 100px)">30</li>
<li style="transform: rotate3d(1, 0, 0, 0deg) translate3d(0, 0, 100px)">1</li>
<li style="transform: rotate3d(1, 0, 0, -20deg) translate3d(0, 0, 100px)">2</li>
<li style="transform: rotate3d(1, 0, 0, -40deg) translate3d(0, 0, 100px)">3</li>
<li style="transform: rotate3d(1, 0, 0, -60deg) translate3d(0, 0, 100px)">4</li>
<li style="transform: rotate3d(1, 0, 0, -80deg) translate3d(0, 0, 100px)">5</li>
</ul>
</div>
</body>
</html>
可以看到我是用到了定位,rotate3d 和 translate3d, 可能你會問為什麼要用到translate3d 并且第三個參數寫100px?
我主要是用到的定位,都定位到一起了,也就是一個黑點了,哈哈。。。 然後用 transform 的 rotate3d 統一 沿Y軸旋轉元素 到一定的角度,然而我們要做滾筒,滾筒需要半徑,是以我用translate3d 拉伸 Z 軸 (垂直螢幕)100px,
這樣元素就沿着我拉伸前的原點旋轉,半徑是 100px; 大家可以複制代碼運作一下,看看效果,如何有其他方法分享出來吧,共同學習進步。
說了這麼多,跟vue有什麼關系呢? 哈哈。。。你猜?
滾動用什麼呢? 我之前用過 scroll ios 需要加上 -webkit-overflow-scrolling: touch; 才能觸發onscroll, 但是那種做法我試了一下,太麻煩,有滾動條,太垃圾。
這裡我們用 touchstart / touchmove / touchend
mounted() {
this.$el.addEventListener('touchstart', this.listenerTouchStart, false);
this.$el.addEventListener('touchmove', this.listenerTouchMove, false);
this.$el.addEventListener('touchend', this.listenerTouchEnd, false);
},
methods: {
listenerTouchStart(ev) {
ev.stopPropagation();
ev.preventDefault();
isInertial = false;
this.finger.startY = ev.targetTouches[0].pageY;
this.finger.prevMove = this.finger.currentMove;
this.finger.startTime = Date.now();
},
listenerTouchMove(ev) {
ev.stopPropagation();
ev.preventDefault();
const move = (this.finger.startY - ev.targetTouches[0].pageY) + this.finger.prevMove;
this.finger.currentMove = move;
this.$refs.wheel.style.transform = `rotate3d(1, 0, 0, ${(move / lineHeight) * singleDeg}deg)`;
this.updateRange(Math.round(move / lineHeight));
},
listenerTouchEnd(ev) {
ev.stopPropagation();
ev.preventDefault();
this.finger.endY = ev.changedTouches[0].pageY;
this.finger.endTime = Date.now();
this.getInertiaDistance();
},
}
我們在 start 時,緩存手觸摸的的Y軸坐标 ,startTime 是為了後面touchend時,計算初速度 (一定距離 時間越短 速度越大,慣性滑動越長)
/**
* 求移動速度(v = s / t),判斷使用者操作快慢,進而得到慣性的滑動距離
*/
getInertiaDistance() {
// 移動距離
const s = this.finger.startY - this.finger.endY;
// 移動時間
const t = this.finger.endTime - this.finger.startTime;
// 移動速度
const v = s / t;
const absV = Math.abs(v);
isInertial = true;
this.inertia(absV, Math.floor(absV / v), 0);
},
/**
* 使用者結束滑動,應該慢慢放慢,最終停止。進而需要 a(加速度)
* @param start 開始速度
* @param position 速度方向,值: 正負1
* @param target 結束速度
*/
inertia(start, position, target) {
if (start <= target || !isInertial) {
this.animate.stop();
this.finger.prevMove = this.finger.currentMove;
this.updateRange(Math.round(this.finger.currentMove / lineHeight));
this.getSelectValue(this.finger.currentMove);
return;
}
// 這段時間走的位移 S = vt + 1/2at^2;
const move = (position * start * (1000 / 60)) + (0.5 * a * (1000 / 60) * (1000 / 60)) + this.finger.currentMove;
// 根據求末速度公式: v末 = v初 + at
const newStart = (position * start) + (a * (1000 / 60));
let moveDeg = (move / lineHeight) * singleDeg;
let actualMove = move;
// 已經到達目标
if (newStart <= target) {
moveDeg = Math.round(move / lineHeight) * singleDeg;
actualMove = Math.round(move / lineHeight) * lineHeight;
this.$refs.wheel.style.transition = 'transform 700ms cubic-bezier(0.19, 1, 0.22, 1)';
} else {
this.$refs.wheel.style.transition = '';
}
this.finger.currentMove = actualMove;
this.$refs.wheel.style.transform = `rotate3d(1, 0, 0, ${moveDeg}deg)`;
this.updateRange(Math.round(this.finger.currentMove / lineHeight));
this.animate.start(this.inertia.bind(this, newStart, position, target));
}
最後我把所有的變量提取出來,到時候能根據使用者要求顯示不同情況
const a = -0.003; // 加速度
const radius = 100; // 半徑
const lineHeight = 36; // 文字行高
let isInertial = false; // 是否正在慣性滑動
// 根據三角形餘弦公式
// 反餘弦得到弧度再轉換為度數,這個度數是單行文字所占有的。
let deg = Math.round((Math.acos((((radius * radius) + (radius * radius)) - (lineHeight * lineHeight)) / (2 * radius * radius)) * 180) / Math.PI);
// deg這個值須360能整除,因為當滾動列占滿一周後可以再次均勻的覆寫在上一周文字上;滾動時不會出現錯位
while (360 % deg !== 0 && deg <= 360) {
deg += 1;
}
const singleDeg = deg;
// 半圓下的内容條數
const space = Math.floor((360 / singleDeg) / 2);
最後附上github源碼連結 https://github.com/zhangKunUserGit/vue-component大家可以下載下傳運作