u-slider.vue 7.0 KB

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