在一些須要用戶填寫資料的業務場景中,有時會讓用戶選擇某個業務的範圍,這時就須要用到滑塊進度條。而後大家最愛的產品經理會說,給我整一個顏色可控,滑塊按鈕可大可小,滑塊邊框也要可大可小的滑動條來..javascript
emmm,一看這樣的設計需求就意味着小程序原生的slider組件就不能用了。由於這玩意在樣式上就不能自由的配置,只好來手動實現一個。css
行吧,那說幹就幹。首先滑動條能夠從俯視圖角度來看,分爲三層。分別是底部滑軌區域
,進度條區域
以及供用戶操做的滑塊
自己。html
在結構設計中,能夠將底部滑軌區域
,進度條區域
分爲一塊,這樣進度條區域
能夠根據隨着滑動條的高度變化而變化, 寬度則由js
控制。除此以外還須要暴露一些參數給外部,讓它本身定義長粗寬。java
Component({ /** * 組件的屬性列表 */ properties: { // 滑塊大小 blockSize: { type: Number, value: 32, }, // 滑塊寬度 blockBorderWidth: { type: Number, value: 3 }, // 滑軌高度 height: { type: Number, value: 2 }, // 滑軌進度 step: { type: Number, value: 0, }, // 進度值小數位 digits: { type: Number, value: 0, }, }, });
<view id="slider-wrap" class="slider-wrap"> <view class="silder-bg" style="height: {{height}}rpx;"> <view class="silder-bg-inner"></view> </view> <view class="silder-block" style="height: {{blockSize}}rpx; border-width: {{blockBorderWidth}}rpx;" ></view> </view>
.slider-wrap { position: relative; display: flex; align-items: center; width: 100%; } .silder-bg, .silder-bg-inner, .silder-block { position: absolute; left: 0; } .silder-bg, .silder-bg-inner { width: 100%; height: 2rpx; flex: 1; } .silder-bg { overflow: hidden; background-color: #eeeeee; border-radius: 8rpx; z-index: 0; } .silder-bg-inner { height: 100%; background-color: #66a6ff; /* border-radius: 8rpx; */ z-index: 1; border-bottom-left-radius: 8rpx; border-top-left-radius: 8rpx; } .silder-block { width: 32rpx; height: 32rpx; background-color: #ffffff; border: solid 3rpx #66a6ff; z-index: 2; border-radius: 50%; box-sizing: border-box; }
滑塊進度條的
滑塊
是一個聽話的小朋友,就是說咱們叫它去哪它就聽話的過去。因此就不要抓它去煲湯了~
在組件外部容器中綁定一個點擊事件,咱們必須得要知道用戶點擊位置,在bind:tap
事件中取到clientX
屬性。除此以外還須要取到進度條的位置信息。git
獲得兩個關鍵數據後,將用戶點擊的位置ClintX
與進度條組件的偏移量offset
相減,得出相對於組件內的進度progress
.
再用組件的寬度width
減去progress
乘於100
獲得目前進度的百分比percentage
。
同時爲了防止進度條超出進度條github
以下圖所示:((191 - 36) / 301) * 100 ≈ 52
小程序
<view class="slider-wrap" bindtap="tappingSlider"> <!-- ...other --> </view>
Component({ // ... /** * 組件的初始數據 */ data: { containerInfo: null, percentage: 0, }, ready() { // 取到滑塊進度條的位置信息 wx.createSelectorQuery().in(this) .select('.slider-wrap') .boundingClientRect((rect) => { if (!rect) return; this.data.container = rect; this._initBloackPos(); }).exec() }, // 點擊進度條 tappingSlider(evt) { const { containerInfo } = this.data; if (!containerInfo) return; const { clientX } = evt.changedTouches[0]; const { digits, _maxDistance } = this.data; // 須要作邊界處理 const perc = this._computeOffset(clientX, containerInfo.left, 100); const percentage = this._boundaryHandler(perc); this.setData({ percentage }); this.triggerEvent('change', { value: percentage.toFixed(digits) * 1 }); }, /** * 計算相對容器的偏移距離 * * @param { Number } x - X 座標 * @param { Number } offset - 偏移量 * @param { Number } maxVal - 在 maxVal 範圍內求百分比 */ _computeOffset(x, offset, maxVal) { const { width } = this.data.containerInfo; // 底層保證必定精度 return (((x - offset) / width) * maxVal).toFixed(4) * 1; }, /** * 邊界處理 * @param { Number } num - 待處理的最值 * @param { Number } maxNum - num 最大值 * @param { Number } minNum - num 最小值 */ _boundaryHandler(num, maxNum = 100, minNum = 0) { return num > maxNum ? maxNum : (num < minNum ? minNum : num); }, });
<view class="slider-wrap" bindtap="tappingSlider" bindtouchmove="onTouchMove"> <view class="silder-bg" style="height: {{height}}rpx;"> <view class="silder-bg-inner" style="width: {{percentage}}%; height: {{height}}rpx;" ></view> </view> <view class="silder-block" style="left: {{percentage}}%;width: {{blockSize}}rpx;height: {{blockSize}}rpx; border-width: {{blockBorderWidth}}rpx;" ></view> </view>
雖然實現了點擊滑動到指定位置的功能,但仔細一看仍是有一些瑕疵的~ 當咱們點擊到百分百時,滑塊
超出原先設定的容器寬度。微信
超出的緣由是由於在佈局上,咱們使用絕對定位absolute
,經過設置滑塊left
屬性來控制滑塊位置的。
偏移量中還包含了滑塊自身的寬度,所以還須要對滑塊的偏移量作必定的處理,去掉自身寬度再獲取百分比。app
在文章開頭咱們已經暴露了一個blockSize
的屬性,利用該屬性能夠計算滑塊的最大偏移量:ide
Component({ // ... data: { // other data... _blockOffset: 0, _maxDistance: 100, }, methods: { // 點擊進度條 tappingSlider(evt) { const { containerInfo } = this.data; if (!containerInfo) return; const { clientX } = evt.changedTouches[0]; const { digits, _maxDistance } = this.data; const computeOffset = (maxVal) => { return this._computeOffset(clientX, containerInfo.left, maxVal); } // 滑塊偏移度 const _blockOffset = this._boundaryHandler( computeOffset(_maxDistance), _maxDistance ); // 實際百分比 const percentage = this._boundaryHandler(computeOffset(100)); this.setData({ _blockOffset, percentage }); this.triggerEvent('change', { value: percentage.toFixed(digits) * 1 }); }, } })
<!-- other code --> <view class="silder-block" style="left: {{_blockOffset}}%;width: {{blockSize}}rpx;height: {{blockSize}}rpx; border-width: {{blockBorderWidth}}rpx;" ></view>
如此,該事件就完成啦~
完成點擊事件後,咱們還得讓它能進行自由的滑動。進度條組件的拖動的流程大體是:點擊滑塊
-> 拖動滑塊
-> 釋放滑塊
這三個步驟。
所以跟H5的思路同樣,咱們只需監聽touchmove
、touchstatr
、touchend
三個事件。
首先先監聽touchmove
,用戶點擊滑塊後,記錄當前的clientX
屬性, 隨後還須要記錄當前進度
和滑塊的偏移量
;touchmove
事件則由外層容器相關聯,並更新滑動的距離。因爲touchmove
裏針對拖動事件
邏輯不能被隨便觸發,所以須要加一個標識的鎖;
在touchend
事件觸發後釋放鎖便可:
Component({ methods: { onTouchStart(evt) { this.data.moving = true; // 記錄原始座標 this.data.originPos = this.data._blockOffset; this.data.originPercentage = this.data.percentage; this.data._startTouchX = evt.changedTouches[0].clientX; }, // 滑塊移動 onTouchMove(evt) { const { moving, containerInfo } = this.data; if (!moving || !containerInfo) return; const { clientX } = evt.changedTouches[0]; const { digits, originPos, originPercentage, _startTouchX, _maxDistance } = this.data; // 計算偏移量 const computeOffset = (maxVal) => { return this._computeOffset(clientX, _startTouchX, maxVal); } // 實際百分比 const perc = originPercentage + computeOffset(100); const percentage = this._boundaryHandler(perc); // 滑塊偏移度 const offset = originPos + computeOffset(_maxDistance); const _blockOffset = this._boundaryHandler(offset, _maxDistance); this.setData({ percentage, _blockOffset }); this.triggerEvent('change', { value: percentage.toFixed(digits) * 1 }); }, onTouchEnd(evt) { this.data.moving = false; }, } })
<view class="slider-wrap" bindtap="tappingSlider" bindtouchmove="onTouchMove"> <view class="silder-bg" style="height: {{height}}rpx;"> <view class="silder-bg-inner" style="width: {{percentage}}%; height: {{height}}rpx;" ></view> </view> <view class="silder-block" style="left: {{_blockOffset}}%;width: {{blockSize}}rpx;height: {{blockSize}}rpx; border-width: {{blockBorderWidth}}rpx;" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd" ></view> </view>
以上就是滑塊進度條
組件的實現~ 實際上該組件還有更多可供配置的地方,如顏色值,背景控制等這些比較基礎的東西就不繼續展開講啦~
本文是以小程序進行示例。但思路是共通的,也可使用一樣思路在H5
實現,只不過是 API 的差別罷了~
微信代碼片斷, 能夠直接拿來就用。
2019/05/04 更新:
後面又從新看了一遍,發現該組件仍是有可優化的空間:
操做沒必要侷限於滑塊上,能夠將bindtap
事件廢棄,其他的全部事件都代理到最外部的節點中。touchstar
的同時就渲染位置信息,還容許它自由的滑動:
<view class="slider-wrap" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" > <view class="silder-bg" style="height: {{height}}rpx;"> <view class="silder-bg-inner" style="width: {{percentage}}%; height: {{height}}rpx;" ></view> </view> <view class="silder-block" style="left: {{_blockOffset}}%;width: {{blockSize}}rpx;height: {{blockSize}}rpx; border-width: {{blockBorderWidth}}rpx;" ></view> </view>
Component({ // other options ... methods: { // other method ... onTouchStart(evt) { this.data.moving = true; const { containerInfo } = this.data; if (!containerInfo) return; const { clientX } = evt.changedTouches[0]; const { digits, _maxDistance } = this.data; const computeOffset = (maxVal) => { return this._computeOffset(clientX, containerInfo.left, maxVal); } // 滑塊偏移度 const _blockOffset = this._boundaryHandler( computeOffset(_maxDistance), _maxDistance ); // 實際百分比 const percentage = this._boundaryHandler(computeOffset(100)); // 記錄原始座標 this.data.originPos = _blockOffset; this.data.originPercentage = percentage; this.data._startTouchX = clientX; this.setData({ _blockOffset, percentage }); this.triggerEvent('change', { value: percentage.toFixed(digits) * 1 }); }, } });
原文出自:【UI組件】來作一個可配置的滑塊進度條吧