#yyds干货盘点#【愚公系列】2022年12月 微信小程序-slider滑动选择器详解

时间:2022-12-13 07:16:59

前言

小程序中滑动选择器相关属性如下:

属性 类型 默认值 必填 说明 最低版本
min number 最小值 1.0.0
max number 100 最大值 1.0.0
step number 1 步长,取值必须大于 0,并且可被(max - min)整除 1.0.0
disabled boolean false 是否禁用 1.0.0
value number 当前取值 1.0.0
color color #e9e9e9 背景条的颜色(请使用 backgroundColor) 1.0.0
selected-color color #1aad19 已选择的颜色(请使用 activeColor) 1.0.0
activeColor color #1aad19 已选择的颜色 1.0.0
backgroundColor color #e9e9e9 背景条的颜色 1.0.0
block-size number 28 滑块的大小,取值范围为 12 - 28 1.9.0
block-color color #ffffff 滑块的颜色 1.9.0
show-value boolean false 是否显示当前 value 1.0.0
bindchange eventhandle 完成一次拖动后触发的事件,event.detail = {value} 1.0.0
bindchanging eventhandle 拖动过程中触发的事件,event.detail = {value} 1.7.0

一、slider滑动选择器

1.wxml

<view class="page__bd">
	<view class="section section_gap">
		<text class="section__title">设置step</text>
		<view class="body-view">
			<slider bindchange="slider2change" step="5" />
		</view>
	</view>
	<view class="section section_gap">
		<text class="section__title">显示当前value</text>
		<view class="body-view">
			<slider bindchange="slider3change" show-value />
		</view>
	</view>
	<!-- 测试changing事件 -->
	<view class="section section_gap">
		<text class="section__title">设置最小/最大值</text>
		<view class="body-view">
			<slider bindchange="slider4change" bindchanging="onSliderChanging" min="50" max="200" show-value />
		</view>
	</view>
</view>

2.js

var pageData = {}
for (var i = 1; i < 5; ++i) {
  (function (index) {
    pageData[`slider${index}change`] = function (e) {
      console.log(`slider${index}发生change事件,携带值为`, e.detail.value)
    }
  })(i);
}
Page(Object.assign({
  data:{},
  onSliderChanging(e){
    console.log(e.type, e.detail.value);
  },
}, pageData))

3.效果

#yyds干货盘点#【愚公系列】2022年12月 微信小程序-slider滑动选择器详解

二、自定义滑动选择器

1.组件的封装

index.js

Component({
  properties: {
    blockColor: {
      type: String,
      value: "#ffffff"
    },
    blockSize: {
      type: Number,
      value: 28
    },
    backgroundColor: {
      type: String,
      value: "#e9e9e9"
    },
    activeColor: {
      type: String,
      value: "#1aad19"
    },
    step: {
      type: Number,
      value: 1
    },
    min: {
      type: Number,
      value: 0
    },
    max: {
      type: Number,
      value: 100
    },
    value: {
      type: Number,
      value: 0
    },
    disabled: {
      type: Boolean,
      value: false
    },
    showValue: {
      type: Boolean,
      value: false
    },
  },
  observers: {
    'blockSize': function(blockSize) {
      // 这个地方是规范blockSize的最大、最小值
      if (blockSize > 28) {
        this.setData({
          blockSize: 28
        })
      } else if (blockSize < 12) {
        this.setData({
          blockSize: 12
        })
      }
    },
    'showValue': function(){
      this.queryHeight() // 由于显示数字后,滑动区域变化,需要重新查询可滑动高度
      console.log('showValue----',this.properties.showValue);
    }
  },
  data: {
    totalTop: null,
    totalHeight: null,
    currentValue: 0,
  },
  ready(){
    // 适合在这里调用
    this.queryHeight()
  },
  methods: {
    // 设置当前值是多少,是加上最小值之后的当前值
    setCurrent: function(e){
      this.setData({
        currentValue: e
      })
    },
    queryHeight: function(){
      wx.createSelectorQuery().in(this).select('.slider-container').boundingClientRect((res) => {
        this.setData({
          totalTop: res.top,
          totalHeight: res.height
        })
      }).exec()
    },
    empty: function(){},
  }
})

index.wxml

Component({
  properties: {
    blockColor: {
      type: String,
      value: "#ffffff"
    },
    blockSize: {
      type: Number,
      value: 28
    },
    backgroundColor: {
      type: String,
      value: "#e9e9e9"
    },
    activeColor: {
      type: String,
      value: "#1aad19"
    },
    step: {
      type: Number,
      value: 1
    },
    min: {
      type: Number,
      value: 0
    },
    max: {
      type: Number,
      value: 100
    },
    value: {
      type: Number,
      value: 0
    },
    disabled: {
      type: Boolean,
      value: false
    },
    showValue: {
      type: Boolean,
      value: false
    },
  },
  observers: {
    'blockSize': function(blockSize) {
      // 这个地方是规范blockSize的最大、最小值
      if (blockSize > 28) {
        this.setData({
          blockSize: 28
        })
      } else if (blockSize < 12) {
        this.setData({
          blockSize: 12
        })
      }
    },
    'showValue': function(){
      this.queryHeight() // 由于显示数字后,滑动区域变化,需要重新查询可滑动高度
      // 如果被设置了,相应的observer是自动触发的
      // 因为这个组件在使用时,设置了showValue属性,所以queryHeight被调用了
      // 如果没有设置,这个函数是不会被调用的
      console.log('showValue----',this.properties.showValue);
    }
  },
  data: {
    totalTop: null,
    totalHeight: null,
    currentValue: 0,
  },
  ready(){
    // 适合在这里调用
    this.queryHeight()
  },
  methods: {
    // 设置当前值是多少,是加上最小值之后的当前值
    setCurrent: function(e){
      this.setData({
        currentValue: e
      })
    },
    queryHeight: function(){
      // 这个地方是为了取出滚动容器在页面中的起点,距离页面顶边的y距离是多少totalTop
      // totalTop相当于startY
      // 还有整个滑动容器的高度totalHeight,相当于sliderHeight
      // 因在wxs模块中组件或页面描述对象,没有createSelectorQuery方法,所以这个查询只能放在逻辑层js中进行,好在这个查询并不需要重复执行
      // 整个totalHeight,要被min,max的差,以step为粒度平分掉
      wx.createSelectorQuery().in(this).select('.slider-container').boundingClientRect((res) => {
        this.setData({
          totalTop: res.top,
          totalHeight: res.height
        })
      }).exec()
    },
    empty: function(){},
  }
})

index.wxs

var notInt = function(num) {
  return num !== parseInt(num)
}
/*
 * state:共享临时数据对象
 * state.max:最大值
 * state.min:最小值
 * state.offset:当前高度,即value - min的值(未按照step纠正的值)
 * state.step:步长
 * ins:页面或组件实例
 */
// 这个函数主要是为了计算,当value变化时,计算出上、下两部分灰、绿色各占百分多少
// 然后通过组件描述对象设置样式
// 这个方法会在第一次属性设置中、调用
var calculate = function(instance,state,changeCallback){
  var max = state.max
  var min = state.min
  var offset = state.offset
  var step = state.step
  // 1、计算 offset 按照 step 算应该是几个。
  // Math.round函数,是大于等于0.5算一步,否则不算
  // Math.round(offset % step / step) 计算的是 offset 对 step 取模后剩下的长度四舍五入,就是多出来的部分是否该算一步
  // 
  // Math.floor(offset / step) 计算的是 offset 中包含多少个完整的 step
  var stepNum = Math.round(offset % step / step) + Math.floor(offset / step)
  // 2、纠正后的当前高度
  // 当前的步值数
  var current = stepNum * step

  // 3、当前高度所占比例,由于 offset 的大小已经在进方法前经过了修正,所以这里不需要再判断是否小于0或者大于100了
  // 算出百分比
  var percent = current * 100 / (max - min)
  // var percent = (current * 100 / (max - min)).toFixed(2)

  // 设置上灰色是多少高度,百分比
  // value是从底部开始计算的,所以这里是100-percent
  instance.selectComponent("#upper").setStyle({
    height: (100 - percent) + "%"
  })
  // 设置选中的绿色部分百分比
  // instance.selectComponent("#lower").setStyle({
  //   height: percent + "%"
  // })
  // 如果值有变化,调用回调函数
  if(state.current !== current){
    state.current = current
    changeCallback(current+min)
  }
}

module.exports = {
  // 这是由wxsPropObserver机制调用的
propsChange: function(newValue, oldValue, ownerIns, ins) {
  var state = ownerIns.getState()
  var step = newValue.step;
  var min = newValue.min;
  var max = newValue.max;
  // value是设置的当前值
  var value = newValue.value;
  if (notInt(step) || notInt(min) || notInt(max) || notInt(value)) {
    console.log("你不把 step min max value 设成正整数,我没法做啊")
    return
  }
  if (min > max) {
    min = oldValue.min
    max = oldValue.max
  }
  if (value > max) {
    console.log("value的值比max大,将value强制设为max")
    value = max
  } else if (value < min) {
    console.log("value的值比min小,将value强制设为min:"+min)
    value = min
  }
  if (step <= 0 || (max - min) % step != 0) {
    console.log("step只能是正整数且必须被(max-min)整除,否则将step强制设为1")
    step = 1
  }
  state.min = min
  state.max = max
  state.step = step
  state.offset = value - min
  state.disabled = newValue.disabled
  state.totalTop = newValue.totalTop
  state.totalHeight = newValue.totalHeight
  if (newValue.totalTop !== null && newValue.totalHeight !== null) {
    calculate(ownerIns, state, function(currentValue){
      ownerIns.callMethod("setCurrent", state.current + state.min)
    })
  }
},
  // 这是由顶点、底点单击时调用的
  // 直接在最大、最小值之间切换
  // 因为在组件上设置了dataset属性,所以可以用一种函数
  // 
tapEndPoint: function(e, ins){
  var state = ins.getState()
  if (state.disabled) return
  var percent = e.currentTarget.dataset.percent
  state.offset = (state.max - state.min) * percent
  calculate(ins, state, function (currentValue) {
    ins.triggerEvent("change", {
      value: currentValue
    })
    ins.callMethod("setCurrent", currentValue)
  })
},
  // 单击upper、lower两处或灰色、或绿色,都是调用这个函数
  tap: function(e, ins) {
    var state = ins.getState()
    if (state.disabled) return
    // (总高度+头部高度-点击点高度)/ 总高度 = 点击点在组件的位置
    // 点击事件只在线条上,所以percent是不可能小于0,也不可能超过100%,无需另加判断
    // 计算从滑块底部,到音击点之间的距离,再除为总高度,计算出百分比
    var percent = (state.totalTop + state.totalHeight - e.changedTouches[0].pageY) / state.totalHeight
    // 依据百分比,计算出偏移量数量,这个数量与上面计算出来的从滑块底部到音击点之间的距离,并不一定一致,因为单位是由调用者定义的
    state.offset = (state.max - state.min) * percent
    // 最后调用计算函数,完成后派发事件,设置逻辑层的当前值
    calculate(ins, state, function(currentValue){
      ins.callMethod("setCurrent", currentValue)
      ins.triggerEvent("change", {
        value: currentValue
      })
    })
  },
  // 滑块开始滑动时,记录当前坐标,及当前的current值
  // 由于单击点,与当前滑块的高度,只能作一个绑定,并不能完全等同,因为每次单击的点并不太一样
start: function(e, ins) {
  var state = ins.getState()
  if (state.disabled) return
  state.startPoint = e.changedTouches[0]
  // 本次滑动之前的高度px = 当前高度value / (最大值-最小值) * 最大高度
  var currentPx = state.current / (state.max - state.min) * state.totalHeight
  state.currentPx = currentPx
},
// 滑块开始移动了
// getState是获取模块变量对象,可以在模块内共享利用,因为没有this,模块内并不方便在各个方法之间共享变量
move: function(e, ins) {

  var state = ins.getState()
  if (state.disabled) return
  // 
  var startPoint = state.startPoint
  var endPoint = e.changedTouches[0]
  console.log('endPoint',JSON.stringify(endPoint));
  
  // 当前的高度px = 滑动之前的高度px + 起始点高度 - 当前点高度
  // 依据移动点,计算出偏移之后的当前值是多少
  var currentPx = state.currentPx + startPoint.pageY - endPoint.pageY
  // 计算出百分比
  var percent = currentPx / state.totalHeight
  // 由于可能滑出slider范围,所以要限制比例在 0-1之间
  // 有可能会移动范围之处,值可能会超过0~1的范围
  percent = percent>1?1:percent
  percent = percent<0?0:percent
  state.offset = (state.max - state.min) * percent
  calculate(ins, state, function(currentValue){
    ins.triggerEvent("changing", {
      value: currentValue
    })
    ins.callMethod("setCurrent", currentValue)
  })
},
// 滑块结束
end: function(e, ins) {
  var state = ins.getState()
  if (state.disabled) return
  ins.triggerEvent("change", {
    value: state.current + state.min
  })
}
}

index.wxss

.slider {
  height: 100%;
  padding: 30rpx 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
}
.slider-container {
  flex: 1;
  margin: 0 20px;
  width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.slider-upper {
  flex-shrink: 0;
  height: 100%;
}
/* 
flex-shrink这个样式是决定flex元素的收缩规则的
并且,它不是决定元素的所占宽度比,而是决定了不足的空间,各个元素各分摊多少的缩小额度
*/
.slider-lower {
  flex-shrink: 0;
  height: auto;
  flex:1;
}
.slider-upper-line {
  height: 100%;
  margin: 0 12px;
  width: 2px;
}
.slider-lower-line {
  height: 100%;
  margin: 0 12px;
  width: 2px;
}
.slider-middle {
  flex-shrink: 1;
  width: 0;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.slider-block {
  flex-shrink: 0;
  width: 20rpx;
  height: 20rpx;
  border-radius: 50%;
  position: relative;
  z-index: 1;
}
.slider-value {
  flex-shrink: 0;
  pointer-events: none;
}
.slider-append {
  flex-shrink: 0;
  height: 10px;
  padding: 0 20px;
}

2.组件的使用

{
  "usingComponents": {
    "slider-vertical": "../../components/vertical-slider/index"
  },
}
<view class="section section_gap">
	<text class="section__title">自定义竖向slider</text>
	<view class="body-view">
		<view style="height: 400rpx;margin: 20px;display: flex;justify-content: space-around">
		
			<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="1" min="50" max="200" value="0" disabled="{{false}}" show-value="{{true}}"></slider-vertical>

			<slider-vertical block-color="#ffffff" block-size="28" backgroundColor="#e9e9e9" activeColor="#1aad19" bindchange="slider1change" bindchanging="slider1changing" step="5" min="50" max="200" value="115" disabled="{{false}}" show-value="{{false}}"></slider-vertical>
		</view>
	</view>
</view>
  slider1change: function (e) {
    console.log("change:",e)
  },
  slider1changing: function (e) {
    console.log("changing:",e)
  }

效果 #yyds干货盘点#【愚公系列】2022年12月 微信小程序-slider滑动选择器详解