從零實現Vue的元件庫(二)- Slider 實現

FatGe發表於2018-12-20

實現一個Slider元件,方便使用者通過拖動滑塊在一個固定區間內進行選擇,增強互動細節。

概述: 在使用者手動一些限定數字時,如果採用輸入框的形式,會需要提示資訊和錯誤資訊來引導使用者,這就存在一些冗餘操作。所以衍生出Slider元件,方便使用者拖動來選定一個值。

該元件的痛點在於:
  • 相容不同遊覽器的樣式;
  • Slider元件的value、min、max之間的關係,以及對樣式的影響。

1. 例項

最終效果

程式碼

<!-- 基礎用法 -->
<fat-slider />
<!-- 設定Min、Max以及Step -->
<fat-slider :min="20" :max="40" step="5" />
<!-- 繫結v-model -->
<fat-slider v-model="initValue" />
<!-- 不現實Tool-Tip -->
<fat-slider v-model="value" :show-tooltip="false" />
<!-- disabled 該元件 -->
<fat-slider disabled />
複製程式碼

例項地址:Slider 例項

程式碼地址:Github UI-Library

2. 原理

該元件的實現是基於原生的<input type="range" />,再通過改寫樣式以達到上圖效果。

元件的基本結構如下

<template>
  <div :class="['slider-wrap', { 'is-disabled': disabled }]">
    <input
      :class="['slider-inner', { 'is-disabled': disabled }]"
      :disabled="disabled"
      :min="min"
      :max="max"
      type="range"
      v-on="$listeners"
      v-model="rate"
      v-bind="$attrs"
    >
  </div>
</template>
複製程式碼
  • 先將修改元件的thumb以及軌道track的樣式:

將其從原生的形式

從零實現Vue的元件庫(二)- Slider 實現

變成以下的樣式

從零實現Vue的元件庫(二)- Slider 實現

基於cross-browser-range-input這篇博文,進行基礎樣式的修改。

為了相容不同的遊覽器,首先利用@mixin抽離出thumb公共的樣式。

@mixin thumb-common-style() {
    position: relative;
    z-index: 3;
    border: 2px solid #409eff;
    background: #fff;
    cursor: grab;
    transition: all 0.3s;
}
// 後續可以豐富rect-slider-thumb等型別
@mixin circle-slider-thumb() {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    &:active {
        cursor: grabbing;
    }
}
複製程式碼

然後適配不同的瀏覽器

&::-webkit-slider-thumb {
    // thumb居中
    margin-top: -6px;
    @include circle-slider-thumb();
    @include thumb-common-style();
}
&::-moz-range-thumb {
    @include circle-slider-thumb();
    @include thumb-common-style();
    // z-index無效
    transform: translateZ(1px);
}
&::-ms-thumb {
    @include circle-slider-thumb();
    @include thumb-common-style();
}
複製程式碼

然後以同樣的方法來處理track的樣式

@mixin common-track {
    width: 100%;
    height: 6px;
    background: #e4e7ed;
    border-radius: 3px;
    cursor: pointer;
}
複製程式碼

適配不同瀏覽器

    &::-webkit-slider-runnable-track {
        @include common-track();
    }
    &::-moz-range-track {
        @include common-track();
    }
    /* 只有ms支援fill-lower、fill-upper */
    &::-ms-track {
        width: 100%;
        height: 6px;
        cursor: pointer;
        background: transparent;
        border-color: transparent;
        border-width: 16px 0;
        color: transparent;
    }
    &::-ms-fill-lower {
        background: #409eff;
        border-radius: 3px;
    }
    &::-ms-fill-upper {
        background: #c4c4c4;
        border-radius: 3px;
    }
複製程式碼

此時Slider元件在不同瀏覽器下的顯示,如下圖

  • Chrome

    從零實現Vue的元件庫(二)- Slider 實現
  • Firefox

    從零實現Vue的元件庫(二)- Slider 實現
  • Ie11

    從零實現Vue的元件庫(二)- Slider 實現

此時的Ie11優秀的不得了,不僅提供了fill-upperfill-lower還自帶tool-tip提示功能。為了讓其他瀏覽器向他靠齊,就需要實現上述兩個功能。豐富元件的結構為

<div :class="['slider-wrapper', { 'is-disabled': disabled }]">
    <div v-if="!isIE" class="progress" :style="progressStyle"></div>
    <input
      :class="['slider-inner', { 'is-disabled': disabled }]"
      :disabled="disabled"
      :min="min"
      :max="max"
      type="range"
      v-on="$listeners"
      v-model="rate"
      v-bind="$attrs"
    >
    <span v-if="!isIE && showTooltip" class="tooltip" :style="toolTipPosition">
    {{ rate }}</span>
</div>
複製程式碼

元件中progresstooltip的樣式需要通過當前的rate值來進行修改,其規則為

computed: {
    isIE() {
        return (
            !!window.ActiveXObject ||
            "ActiveXObject" in window ||
            navigator.userAgent.indexOf("Edge") > -1
        );
    },
    progressStyle() {
        const { rate, max, min } = this;
        
        return {
            width: `${((rate - min) * 100) / (max - min)}%`
        };
    },
    toolTipPosition() {
        const { rate, max, min } = this;
        const xOffset = 9 - 18 * ((rate - min) / (max - min));
        
        return {
            left: `${((rate - min) * 100) / (max - min)}%`,
            marginLeft: `${xOffset}px`,
            transform: `translateX(-50%)`
        };
    }
}
複製程式碼

其中progressStyle比較好理解,就是當前rate的值佔整體的百分比,而toolTipPosition則是利用

position: absolute;
/* <percentage>s of the width of the containing block */
left: 10%;
/* <percentage>s of the width of the element */
tansform: translateX(-50%);
複製程式碼

3. 使用

進一步將其封裝成Vue的元件,配置其propsdata

export default {
    props: {
        showTooltip: { type: Boolean, default: true },
        disabled: { type: Boolean, default: false },
        min: { type: Number, default: 0 },
        max: { type: Number, default: 100 },
        value: { type: [Number, String] }
    },
    // 處理v-model
    model: {
        prop: "value",
        event: "sliding"
    },
    watch: {
        // 動態修改
        rate(value) {
            this.$emit("sliding", Number(value));
        },
        // 如果不存在初始值的話,以最小值為初始值
        value: {
            handler(value) {
                this.rate = this.value || this.min;
            },
            immediate: true
        }
    }
}
複製程式碼

4. 總結

封裝一個Slider元件,相容<input type="range" />在不同瀏覽器下的樣式,以及簡化其內部邏輯,方便後續擴充。

往期文章:

原創宣告: 該文章為原創文章,轉載請註明出處。

相關文章