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>
|