tn-slider.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <view
  3. class="tn-slider-class tn-slider"
  4. :class="{'tn-slider--disabled': disabled}"
  5. :style="{
  6. backgroundColor: inactiveColor
  7. }"
  8. @tap="click"
  9. >
  10. <!-- slider滑动线 -->
  11. <view
  12. class="tn-slider__gap"
  13. :style="[
  14. barStyle,
  15. {
  16. height: this.$t.string.getLengthUnitValue(lineHeight),
  17. backgroundColor: activeColor
  18. }
  19. ]"
  20. >
  21. <!-- slider滑块 -->
  22. <view
  23. class="tn-slider__button-wrap"
  24. @touchstart="touchStart"
  25. @touchmove="touchMove"
  26. @touchend="touchEnd"
  27. @touchcancel="touchEnd"
  28. >
  29. <view v-if="$slots.default || $slots.$default">
  30. <slot></slot>
  31. </view>
  32. <view
  33. v-else
  34. class="tn-slider__button"
  35. :style="[blockStyle, {
  36. height: this.$t.string.getLengthUnitValue(blockWidth),
  37. width: this.$t.string.getLengthUnitValue(blockWidth),
  38. backgroundColor: blockColor
  39. }]"
  40. ></view>
  41. </view>
  42. </view>
  43. </view>
  44. </template>
  45. <script>
  46. export default {
  47. name: 'tn-slider',
  48. props: {
  49. // 进度值
  50. value: {
  51. type: [Number, String],
  52. default: 0
  53. },
  54. // 最小值
  55. min: {
  56. type: Number,
  57. default: 0
  58. },
  59. // 最大值
  60. max: {
  61. type: Number,
  62. default: 100
  63. },
  64. // 步进值
  65. step: {
  66. type: Number,
  67. default: 1
  68. },
  69. // 禁用
  70. disabled: {
  71. type: Boolean,
  72. default: false
  73. },
  74. // 滑块宽度
  75. blockWidth: {
  76. type: Number,
  77. default: 30
  78. },
  79. // 滑动条高度
  80. lineHeight: {
  81. type: Number,
  82. default: 8
  83. },
  84. // 滑动条激活的颜色
  85. activeColor: {
  86. type: String,
  87. default: '#01BEFF'
  88. },
  89. // 滑动条未被激活的颜色
  90. inactiveColor: {
  91. type: String,
  92. default: '#E6E6E6'
  93. },
  94. // 滑块的颜色
  95. blockColor: {
  96. type: String,
  97. default: '#FFFFFF'
  98. },
  99. // 自定义滑块的样式
  100. blockStyle: {
  101. type: Object,
  102. default() {
  103. return {}
  104. }
  105. }
  106. },
  107. data() {
  108. return {
  109. startX: 0,
  110. status: 'end',
  111. newValue: 0,
  112. distanceX: 0,
  113. startValue: 0,
  114. barStyle: {},
  115. sliderRect: {
  116. left: 0,
  117. width: 0
  118. }
  119. }
  120. },
  121. watch: {
  122. value(val) {
  123. // 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
  124. if (this.status === 'end') this.updateValue(val, false)
  125. }
  126. },
  127. created() {
  128. this.updateValue(this.value, false)
  129. },
  130. mounted() {
  131. this._tGetRect('.tn-slider').then(res => {
  132. this.sliderRect = res
  133. })
  134. },
  135. methods: {
  136. // 开始滑动
  137. touchStart(event) {
  138. if (this.disabled) return
  139. if (!event.changedTouches[0]) return
  140. this.startX = 0
  141. // 触摸点
  142. this.startX = event.changedTouches[0].pageX
  143. this.startValue = this.format(this.value)
  144. // 标识当前开始触摸
  145. this.status = 'start'
  146. },
  147. // 滑动移动中
  148. touchMove(event) {
  149. if (this.disabled) return
  150. if (!event.changedTouches[0]) return
  151. // 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
  152. // 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
  153. if (this.status === 'start') this.$emit('start')
  154. let movePageX = event.changedTouches[0].pageX
  155. // 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
  156. this.distanceX = movePageX - this.sliderRect.left
  157. // 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图
  158. // 否则造成通信阻塞,需要每改变一个step值时修改一次视图
  159. this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + this.min
  160. this.status = 'moving'
  161. this.$emit('moving')
  162. this.updateValue(this.newValue, true)
  163. },
  164. // 滑动结束
  165. touchEnd() {
  166. if(this.disabled) return
  167. if (this.status === 'moving') {
  168. this.updateValue(this.newValue, false)
  169. this.$emit('end')
  170. }
  171. this.status = 'end'
  172. },
  173. // 更新数值
  174. updateValue(value, drag) {
  175. // 去掉小数部分,对step进行步进处理
  176. value = this.format(value)
  177. const width = Math.round((value - this.min) / (this.max - this.min) * 100)
  178. // 不允许滑动的距离小于0和超过100
  179. if (width < 0 || width > 100) return
  180. // 设置移动的百分比
  181. let barStyle = {
  182. width: width + '%'
  183. }
  184. // 移动期间取消动画
  185. if (drag === true) {
  186. barStyle.transition = 'none'
  187. } else {
  188. // 非移动期间,删掉对过渡为空的声明,让css中的声明起效
  189. delete barStyle.transition
  190. }
  191. // 修改value值
  192. this.$emit('input', value)
  193. this.barStyle = barStyle
  194. },
  195. // 点击事件
  196. click(event) {
  197. if (this.disabled) return
  198. // 直接点击的情况,计算方式和touchMove方法一致
  199. const value = (((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * (this.max - this.min)) + this.min
  200. this.updateValue(value, false)
  201. },
  202. // 格式化滑动的值
  203. format(value) {
  204. return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
  205. }
  206. }
  207. }
  208. </script>
  209. <style lang="scss" scoped>
  210. .tn-slider {
  211. width: 100%;
  212. position: relative;
  213. border-radius: 1000rpx;
  214. // 增加点击的范围
  215. border-width: 20rpx;
  216. border-style: solid;
  217. border-color: transparent;
  218. background-color: $tn-font-holder-color;
  219. background-clip: content-box;
  220. &__gap {
  221. position: relative;
  222. border-radius: inherit;
  223. transition: width 0.2s;
  224. background-color: #01BEFF;
  225. }
  226. &__button {
  227. width: 30rpx;
  228. height: 30rpx;
  229. border-radius: 50%;
  230. box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
  231. background-color: #FFFFFF;
  232. cursor: pointer;
  233. &-wrap {
  234. position: absolute;
  235. top: 50%;
  236. right: 0;
  237. transform: translate3d(50%, -50%, 0);
  238. }
  239. }
  240. &--disabled {
  241. opacity: 0.6;
  242. }
  243. }
  244. </style>