u-slider.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <template>
  2. <view class="u-slider" @tap="onClick" :class="[disabled ? 'u-slider--disabled' : '']" :style="{
  3. backgroundColor: inactiveColor
  4. }">
  5. <view class="u-slider__gap" :style="[
  6. barStyle,
  7. {
  8. height: height + 'rpx',
  9. backgroundColor: activeColor
  10. }
  11. ]">
  12. <view class="u-slider__button-wrap" @touchstart="onTouchStart" @touchmove="onTouchMove"
  13. @touchend="onTouchEnd" @touchcancel="onTouchEnd">
  14. <slot v-if="$slots.default || $slots.$default" />
  15. <view v-else class="u-slider__button" :style="[
  16. blockStyle,
  17. {
  18. height: blockWidth + 'rpx',
  19. width: blockWidth + 'rpx',
  20. backgroundColor: blockColor
  21. }
  22. ]"></view>
  23. </view>
  24. </view>
  25. </view>
  26. </template>
  27. <script>
  28. /**
  29. * slider 滑块选择器
  30. * @tutorial https://uviewui.com/components/slider.html
  31. * @property {Number | String} value 滑块默认值(默认0)
  32. * @property {Number | String} min 最小值(默认0)
  33. * @property {Number | String} max 最大值(默认100)
  34. * @property {Number | String} step 步长(默认1)
  35. * @property {Number | String} blockWidth 滑块宽度,高等于宽(30)
  36. * @property {Number | String} height 滑块条高度,单位rpx(默认6)
  37. * @property {String} inactiveColor 底部条背景颜色(默认#c0c4cc)
  38. * @property {String} activeColor 底部选择部分的背景颜色(默认#2979ff)
  39. * @property {String} blockColor 滑块颜色(默认#ffffff)
  40. * @property {Object} blockStyle 给滑块自定义样式,对象形式
  41. * @property {Boolean} disabled 是否禁用滑块(默认为false)
  42. * @event {Function} start 滑动触发
  43. * @event {Function} moving 正在滑动中
  44. * @event {Function} end 滑动结束
  45. * @example <u-slider v-model="value" />
  46. */
  47. export default {
  48. name: "u-slider",
  49. emits: ["update:modelValue", "input", "start", "moving", "end"],
  50. props: {
  51. // 当前进度百分比值,范围0-100
  52. value: {
  53. type: [Number, String],
  54. default: 0
  55. },
  56. modelValue: {
  57. type: [Number, String],
  58. default: 0
  59. },
  60. // 是否禁用滑块
  61. disabled: {
  62. type: Boolean,
  63. default: false
  64. },
  65. // 滑块宽度,高等于宽,单位rpx
  66. blockWidth: {
  67. type: [Number, String],
  68. default: 30
  69. },
  70. // 最小值
  71. min: {
  72. type: [Number, String],
  73. default: 0
  74. },
  75. // 最大值
  76. max: {
  77. type: [Number, String],
  78. default: 100
  79. },
  80. // 步进值
  81. step: {
  82. type: [Number, String],
  83. default: 1
  84. },
  85. // 滑块条高度,单位rpx
  86. height: {
  87. type: [Number, String],
  88. default: 6
  89. },
  90. // 进度条的激活部分颜色
  91. activeColor: {
  92. type: String,
  93. default: "#2979ff"
  94. },
  95. // 进度条的背景颜色
  96. inactiveColor: {
  97. type: String,
  98. default: "#c0c4cc"
  99. },
  100. // 滑块的背景颜色
  101. blockColor: {
  102. type: String,
  103. default: "#ffffff"
  104. },
  105. // 用户对滑块的自定义颜色
  106. blockStyle: {
  107. type: Object,
  108. default () {
  109. return {};
  110. }
  111. }
  112. },
  113. data() {
  114. return {
  115. startX: 0,
  116. status: "end",
  117. newValue: 0,
  118. distanceX: 0,
  119. startValue: 0,
  120. barStyle: {},
  121. sliderRect: {
  122. left: 0,
  123. width: 0
  124. }
  125. };
  126. },
  127. watch: {
  128. valueCom(n) {
  129. // 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
  130. if (this.status == "end") this.updateValue(this.valueCom, false);
  131. }
  132. },
  133. created() {
  134. this.updateValue(this.valueCom, false);
  135. },
  136. mounted() {
  137. // 获取滑块条的尺寸信息
  138. this.$uGetRect(".u-slider").then(rect => {
  139. this.sliderRect = rect;
  140. });
  141. },
  142. computed: {
  143. valueCom() {
  144. // #ifndef VUE3
  145. return this.value;
  146. // #endif
  147. // #ifdef VUE3
  148. return this.modelValue;
  149. // #endif
  150. }
  151. },
  152. methods: {
  153. uGetRect() {
  154. this.$uGetRect('.u-slider').then(rect => {
  155. this.sliderRect = rect;
  156. });
  157. },
  158. onTouchStart(event) {
  159. if (this.disabled) return;
  160. this.startX = 0;
  161. // 触摸点集
  162. let touches = event.touches[0];
  163. // 触摸点到屏幕左边的距离
  164. this.startX = touches.clientX;
  165. // 此处的this.value虽为props值,但是通过$emit('input')进行了修改
  166. this.startValue = this.format(this.valueCom);
  167. // 标示当前的状态为开始触摸滑动
  168. this.status = "start";
  169. },
  170. async onTouchMove(event) {
  171. if (this.disabled) return;
  172. if (this.sliderRect.left == 0 || this.sliderRect.width == 0) await this.uGetRect()
  173. // 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
  174. // 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
  175. if (this.status == "start") this.$emit("start");
  176. let touches = event.touches[0];
  177. // 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
  178. this.distanceX = touches.clientX - this.sliderRect.left;
  179. // 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图
  180. // 否则造成通信阻塞,需要每改变一个step值时修改一次视图
  181. this.newValue = (this.distanceX / this.sliderRect.width) * 100;
  182. this.status = "moving";
  183. // 发出moving事件
  184. this.$emit("moving");
  185. this.updateValue(this.newValue, true);
  186. },
  187. onTouchEnd() {
  188. if (this.disabled) return;
  189. if (this.status === "moving") {
  190. this.updateValue(this.newValue, false);
  191. this.$emit("end");
  192. }
  193. this.status = "end";
  194. },
  195. updateValue(value, drag) {
  196. // 去掉小数部分,同时也是对step步进的处理
  197. const width = this.format(value);
  198. // 不允许滑动的值超过max最大值,百分比也不能超过100
  199. if (width > this.max || width > 100) return;
  200. // 设置移动的百分比值
  201. let barStyle = {
  202. width: width + "%"
  203. };
  204. // 移动期间无需过渡动画
  205. if (drag == true) {
  206. barStyle.transition = "none";
  207. } else {
  208. // 非移动期间,删掉对过渡为空的声明,让css中的声明起效
  209. delete barStyle.transition;
  210. }
  211. // 修改value值
  212. this.$emit("input", width);
  213. this.$emit("update:modelValue", width);
  214. this.barStyle = barStyle;
  215. },
  216. format(value) {
  217. // 将小数变成整数,为了减少对视图的更新,造成视图层与逻辑层的阻塞
  218. return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step;
  219. },
  220. async onClick(event) {
  221. if (this.disabled) return;
  222. // 直接点击滑块的情况,计算方式与onTouchMove方法相同
  223. if (this.sliderRect.left == 0 || this.sliderRect.width == 0) await this.uGetRect()
  224. const value = ((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * 100;
  225. this.updateValue(value, false);
  226. }
  227. }
  228. };
  229. </script>
  230. <style lang="scss" scoped>
  231. @import "../../libs/css/style.components.scss";
  232. .u-slider {
  233. position: relative;
  234. border-radius: 999px;
  235. border-radius: 999px;
  236. background-color: #ebedf0;
  237. }
  238. .u-slider:before {
  239. position: absolute;
  240. right: 0;
  241. left: 0;
  242. content: "";
  243. top: -8px;
  244. bottom: -8px;
  245. z-index: -1;
  246. }
  247. .u-slider__gap {
  248. position: relative;
  249. border-radius: inherit;
  250. transition: width 0.2s;
  251. transition: width 0.2s;
  252. background-color: #1989fa;
  253. }
  254. .u-slider__button {
  255. width: 24px;
  256. height: 24px;
  257. border-radius: 50%;
  258. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  259. background-color: #fff;
  260. cursor: pointer;
  261. }
  262. .u-slider__button-wrap {
  263. position: absolute;
  264. top: 50%;
  265. right: 0;
  266. transform: translate3d(50%, -50%, 0);
  267. }
  268. .u-slider--disabled {
  269. opacity: 0.5;
  270. }
  271. </style>