在一些须要用户填写资料的业务场景中,有时会让用户选择某个业务的范围,这时就须要用到滑块进度条。而后大家最爱的产品经理会说,给我整一个颜色可控,滑块按钮可大可小,滑块边框也要可大可小的滑动条来..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组件】来作一个可配置的滑块进度条吧