[UI組件] 來作一個可配置的滑塊進度條吧

在一些須要用戶填寫資料的業務場景中,有時會讓用戶選擇某個業務的範圍,這時就須要用到滑塊進度條。而後大家最愛的產品經理會說,給我整一個顏色可控,滑塊按鈕可大可小,滑塊邊框也要可大可小的滑動條來..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的思路同樣,咱們只需監聽touchmovetouchstatrtouchend三個事件。

首先先監聽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 });
        },
    }
});

微信代碼片斷 v0.0.2

原文出自:【UI組件】來作一個可配置的滑塊進度條吧

相關文章
相關標籤/搜索