[UI组件] 来做一个可配置的滑块进度条吧

栏目: Html · 发布时间: 5年前

内容简介:在一些需要用户填写资料的业务场景中,有时会让用户选择某个业务的范围,这时就需要用到滑块进度条。然后你们最爱的产品经理会说,给我整一个颜色可控,滑块按钮可大可小,滑块边框也要可大可小的滑动条来..emmm,一看这样的设计需求就意味着小程序原生的

在一些需要用户填写资料的业务场景中,有时会让用户选择某个业务的范围,这时就需要用到滑块进度条。然后你们最爱的产品经理会说,给我整一个颜色可控,滑块按钮可大可小,滑块边框也要可大可小的滑动条来..

emmm,一看这样的设计需求就意味着小程序原生的 slider 组件就不能用了。因为这玩意在样式上就不能自由的配置,只好来手动实现一个。

结构设计

[UI组件] 来做一个可配置的滑块进度条吧

行吧,那说干就干。首先滑动条可以从俯视图角度来看,分为三层。分别是 底部滑轨区域进度条区域 以及供用户操作的 滑块 本身。

在结构设计中,可以将 底部滑轨区域进度条区域 分为一块,这样 进度条区域 可以根据随着滑动条的高度变化而变化, 宽度则由 js 控制。除此之外还需要暴露一些参数给外部,让它自己定义长粗宽。

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 属性。除此之外还需要取到进度条的位置信息。

得到两个关键数据后,将用户点击的位置 ClintX 与进度条组件的偏移量 offset 相减,得出相对于组件内的进度 progress .

再用组件的宽度 width 减去 progress 乘于 100 得到目前进度的百分比 percentage

同时为了防止进度条超出进度条

如下图所示: ((191 - 36) / 301) * 100 ≈ 52

[UI组件] 来做一个可配置的滑块进度条吧

<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 属性来控制滑块位置的。

偏移量中还包含了滑块自身的宽度,因此还需要对滑块的偏移量做一定的处理,去掉自身宽度再获取百分比。

在文章开头我们已经暴露了一个 blockSize 的属性,利用该属性可以计算滑块的最大偏移量:

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组件】来做一个可配置的滑块进度条吧


以上所述就是小编给大家介绍的《[UI组件] 来做一个可配置的滑块进度条吧》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

从颠覆到创新

从颠覆到创新

长江商学院 / 中国友谊出版公司 / 49.00

互联网+时代的汹涌来临,一切我们所熟知的事物都在发生改变,商业模式的剧烈变化正在席卷各行各业,所有坚硬的壁垒都将消散,所有的企业都面临着商业模式的再探索和转型,而商业模式的探索、失败、进化,甚而再回到起点,杀死自己推倒重来,不断颠覆不断创新,不断涅槃不断重生,这不仅仅是这个时代新创公司的特征,也是今天互联网领域所有存活下来的巨头们的轨迹。 本书通过11个典型的互联网企业商业模式转型案例,讲述......一起来看看 《从颠覆到创新》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码