| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 | <template>  <view    class="slider-range"    :class="{ disabled: disabled }"    :style="{ paddingLeft: '60rpx', paddingRight: '60rpx' }"  >    <view class="slider-range-inner" :style="{ height: height + 'rpx' }">      <view        class="slider-bar"        :style="{          height: barHeight + 'rpx',        }"      >        <!-- 背景条 -->        <view          class="slider-bar-bg"          :style="{            backgroundColor: backgroundColor,          }"        ></view>        <!-- 滑块实际区间 -->        <view          class="slider-bar-inner"          :style="{            width: ((values[1] - values[0]) / (max - min)) * 100 + '%',            left: lowerHandlePosition + '%',            backgroundColor: activeColor,          }"        ></view>      </view>      <!-- 滑动块-左 -->      <view        class="slider-handle-block"        :class="{ decoration: decorationVisible }"        :style="{          backgroundColor: blockColor,          width: blockSize + 'rpx',          height: blockSize + 'rpx',          left: lowerHandlePosition + '%',        }"        @touchstart="_onTouchStart"        @touchmove="_onBlockTouchMove"        @touchend="_onBlockTouchEnd"        data-tag="lowerBlock"		v-if="showLeftBlock"      ></view>      <!-- 滑动块-右 -->      <view        class="slider-handle-block"        :class="{ decoration: decorationVisible }"        :style="{          backgroundColor: blockColor,          width: blockSize + 'rpx',          height: blockSize + 'rpx',          left: higherHandlePosition + '%',        }"        @touchstart="_onTouchStart"        @touchmove="_onBlockTouchMove"        @touchend="_onBlockTouchEnd"        data-tag="higherBlock"      ></view>      <!-- 滑块值提示 -->      <view v-if="tipVisible" class="range-tip" :style="lowerTipStyle">{{ format(values[0]) }}</view>      <view v-if="tipVisible" class="range-tip" :style="higherTipStyle">{{ format(values[1]) }}</view>    </view>  </view></template><script>export default {  components: {},  props: {    //滑块区间当前取值    value: {      type: Array,      default: function() {        return [0, 100]      },    },    //最小值    min: {      type: Number,      default: 0,    },    //最大值    max: {      type: Number,      default: 100,    },    step: {      type: Number,      default: 1,    },    format: {      type: Function,      default: function(val) {        return val      },    },    disabled: {      type: Boolean,      default: false,    },	showLeftBlock: {	  type: Boolean,	  default: true,	},    //滑块容器高度    height: {      height: Number,      default: 50,    },    //区间进度条高度    barHeight: {      type: Number,      default: 8,    },    //背景条颜色    backgroundColor: {      type: String,      default: '#e9e9e9',    },    //已选择的颜色    activeColor: {      type: String,      default: '#1aad19',    },    //滑块大小    blockSize: {      type: Number,      default: 36,    },    blockColor: {      type: String,      default: '#fff',    },    tipVisible: {      type: Boolean,      default: false,    },    decorationVisible: {      type: Boolean,      default: true,    },  },  data() {    return {      values: [this.min, this.max],      startDragPos: 0, // 开始拖动时的坐标位置      startVal: 0, //开始拖动时较小点的值    }  },  computed: {    // 较小点滑块的坐标    lowerHandlePosition() {      return ((this.values[0] - this.min) / (this.max - this.min)) * 100    },    // 较大点滑块的坐标    higherHandlePosition() {      return ((this.values[1] - this.min) / (this.max - this.min)) * 100    },    lowerTipStyle() {      if (this.lowerHandlePosition < 90) {        return `left: ${this.lowerHandlePosition}%;`      }      return `right: ${100 - this.lowerHandlePosition}%;transform: translate(50%, -100%);`    },    higherTipStyle() {      if (this.higherHandlePosition < 90) {        return `left: ${this.higherHandlePosition}%;`      }      return `right: ${100 - this.higherHandlePosition}%;transform: translate(50%, -100%);`    },  },  created: function() {},  onLoad: function(option) {},  watch: {    //滑块当前值    value: {      immediate: true,      handler(newVal, oldVal) {        if (this._isValuesValid(newVal) && (newVal[0] !== this.values[0] || newVal[1] !== this.values[1])) {          this._updateValue(newVal)        }      },    },  },  methods: {    _updateValue(newVal) {      // 步长大于区间差,或者区间最大值和最小值相等情况      if (this.step >= this.max - this.min) {        throw new RangeError('Invalid slider step or slider range')      }      let newValues = []      if (Array.isArray(newVal)) {        newValues = [newVal[0], newVal[1]]      }      if (typeof newValues[0] !== 'number') {        newValues[0] = this.values[0]      } else {        newValues[0] = Math.round((newValues[0] - this.min) / this.step) * this.step + this.min      }      if (typeof newValues[1] !== 'number') {        newValues[1] = this.values[1]      } else {        newValues[1] = Math.round((newValues[1] - this.min) / this.step) * this.step + this.min      }      // 新值与原值相等,不做处理      if (this.values[0] === newValues[0] && this.values[1] === newValues[1]) {        return      }      // 左侧滑块值小于最小值时,设置为最小值      if (newValues[0] < this.min) {        newValues[0] = this.min      }      // 右侧滑块值大于最大值时,设置为最大值      if (newValues[1] > this.max) {        newValues[1] = this.max      }      // 两个滑块重叠或左右交错,使两个滑块保持最小步长的间距      if (newValues[0] >= newValues[1]) {        // 左侧未动,右侧滑块滑到左侧滑块之左        if (newValues[0] === this.values[0]) {			if (this.showLeftBlock){				newValues[1] = newValues[0] + this.step			}else{				newValues[1] = newValues[0]			}        } else {          // 右侧未动, 左侧滑块滑到右侧之右          newValues[0] = newValues[1] - this.step        }      }      this.values = newValues      this.$emit('change', this.values)    },    _onTouchStart: function(event) {      if (this.disabled) {        return      }      this.isDragging = true      let tag = event.target.dataset.tag      //兼容h5平台及某版本微信      let e = event.changedTouches ? event.changedTouches[0] : event      this.startDragPos = e.pageX      this.startVal = tag === 'lowerBlock' ? this.values[0] : this.values[1]    },    _onBlockTouchMove: function(e) {      if (this.disabled) {        return      }      this._onDrag(e)    },    _onBlockTouchEnd: function(e) {      if (this.disabled) {        return      }      this.isDragging = false      this._onDrag(e)    },    _onDrag(event) {      if (!this.isDragging) {        return      }      let view = uni        .createSelectorQuery()        .in(this)        .select('.slider-range-inner')      view        .boundingClientRect(data => {          let sliderWidth = data.width          const tag = event.target.dataset.tag          let e = event.changedTouches ? event.changedTouches[0] : event          let diff = ((e.pageX - this.startDragPos) / sliderWidth) * (this.max - this.min)          let nextVal = this.startVal + diff          if (tag === 'lowerBlock') {            this._updateValue([nextVal, null])          } else {            this._updateValue([null, nextVal])          }        })        .exec()    },    _isValuesValid: function(values) {      return Array.isArray(values) && values.length == 2    },  },}</script><style scoped>.slider-range {  position: relative;}.slider-range-inner {  position: relative;  width: 100%;}.slider-range.disabled .slider-bar-inner {  opacity: 0.35;}.slider-range.disabled .slider-handle-block {  cursor: not-allowed;}.slider-bar {  position: absolute;  top: 50%;  left: 0;  right: 0;  transform: translateY(-50%);}.slider-bar-bg {  position: absolute;  width: 100%;  height: 100%;  border-radius: 10000px;  z-index: 10;}.slider-bar-inner {  position: absolute;  width: 100%;  height: 100%;  border-radius: 10000rpx;  z-index: 11;}.slider-handle-block {  position: absolute;  top: 50%;  border: 4rpx solid #fff;  transform: translate(-50%, -50%);  border-radius: 50%;  box-shadow: 0 0 6rpx 4rpx rgba(227, 229, 241, 0.5);  z-index: 12;}/* .slider-handle-block.decoration::before {  position: absolute;  content: '';  width: 6upx;  height: 24upx;  top: 50%;  left: 29%;  transform: translateY(-50%);  background: #000;  border-radius: 3upx;  z-index: 13;} *//* .slider-handle-block.decoration::after {  position: absolute;  content: '';  width: 6upx;  height: 24upx;  top: 50%;  right: 29%;  transform: translateY(-50%);  background: #eeedf2;  border-radius: 3upx;  z-index: 13;} */.range-tip {  position: absolute;  top: 0;  font-size: 24upx;  color: #666;  transform: translate(-50%, -100%);}</style>
 |