zmm-slider-verify.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <template>
  2. <view class="zmm-slider-verify" v-if="isShow" @touchmove.stop.prevent="stopMoveHandle">
  3. <view class="zmm-slider-verify-mask" :style="{ 'background-color': maskColor }"></view>
  4. <view class="zmm-slider-verify-wrap" :style="{ 'background-color': wrapColor }">
  5. <view class="zmm-slider-verify-top">
  6. <text class="zmm-slider-verify-title">{{ title }}</text>
  7. <text class="zmm-slider-verify-close" @click="closeHandle">关闭</text>
  8. </view>
  9. <view class="zmm-slider-verify-tips">
  10. <text class="zmm-slider-verify-tips-text">{{ tips }}</text>
  11. </view>
  12. <view class="zmm-slider-verify-box">
  13. <image class="zmm-slider-verify-img" v-if="verifyImg" :src="verifyImg" mode="scaleToFill"></image>
  14. <image class="zmm-slider-verify-img" v-else src="@/uni_modules/zmm-slider-verify/static/img/Verify.jpg"
  15. mode="scaleToFill"></image>
  16. <!-- 右侧用来验证的滑块 -->
  17. <view class="zmm-slider-verify-block-verify" :style="blockVerifyStyle"></view>
  18. <!-- 被css操控的滑块 -->
  19. <view class="zmm-slider-verify-block-move" :style="blockMoveStyle"></view>
  20. <!-- 手指触摸的滑块 -->
  21. <view class="zmm-slider-verify-block-touch" :style="blockTouchStyle" @touchstart="touchstartHandle"
  22. @touchmove="touchmoveHandle" @touchend="touchendHandle" @mousedown="mousedownHandle"
  23. @mousemove="mousemoveHandle" @mouseup="mouseupHandle">
  24. </view>
  25. </view>
  26. <view class="zmm-slider-verify-slider" v-if="showBottomSlider">
  27. <!-- 被css操控的滑块 -->
  28. <view class="zmm-slider-verify-slider-move" :style="sliderMoveStyle"></view>
  29. <!-- 手指触摸的滑块 -->
  30. <view class="zmm-slider-verify-slider-touch" :style="sliderTouchStyle" @touchstart="touchstartHandle"
  31. @touchmove="touchmoveHandle" @touchend="touchendHandle" @mousedown="mousedownHandle"
  32. @mousemove="mousemoveHandle" @mouseup="mouseupHandle">
  33. </view>
  34. </view>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. /**
  40. * zmm-slider-verify 滑动验证组件
  41. * @description 滑动验证组件
  42. * @tutorial https://ext.dcloud.net.cn/plugin?id=10513
  43. * @property {String} title 弹窗标题
  44. * @property {String} tips 弹窗提示
  45. * @property {Number} slideSize 滑块大小
  46. * @property {String} slideColor 滑块颜色
  47. * @property {String} maskColor 遮罩层背景色
  48. * @property {String} wrapColor 主题背景色
  49. * @property {String} verifyImg 图片
  50. * @property {Number} between 校验正负差值区间像素
  51. * @property {Boolean} showBottomSlider 是否显示底部滑动条
  52. * @property {String} bottomSlideSize 底部滑块大小
  53. * @property {String} bottomSlideColor 底部滑块颜色
  54. * @event {Function} success 验证通过事件
  55. * @event {Function} error 验证失败事件
  56. * @event {Function} close 组件关闭事件
  57. */
  58. export default {
  59. name: 'zmmSliderVerify',
  60. emits: ['success', 'error', 'close'],
  61. props: {
  62. //标题
  63. title: {
  64. type: String,
  65. default: '滑动校验'
  66. },
  67. //提醒
  68. tips: {
  69. type: String,
  70. default: '请将左侧透明滑块拖进白色框内'
  71. },
  72. //滑块大小
  73. slideSize: {
  74. type: Number,
  75. default: 40
  76. },
  77. //滑块颜色
  78. slideColor: {
  79. type: String,
  80. default: 'rgba(0,0,0,0.4)'
  81. },
  82. //遮罩层背景色
  83. maskColor: {
  84. type: String,
  85. default: 'rgba(0,0,0,0.4)'
  86. },
  87. // 图片
  88. verifyImg: {
  89. type: String,
  90. default: ''
  91. },
  92. //主体背景色
  93. wrapColor: {
  94. type: String,
  95. default: '#ffffff'
  96. },
  97. //校验正负差值区间像素
  98. between: {
  99. type: Number,
  100. default: 10
  101. },
  102. // 是否显示底部滑动条
  103. showBottomSlider: {
  104. type: Boolean,
  105. default: true
  106. },
  107. //底部滑块大小
  108. bottomSlideSize: {
  109. type: Number,
  110. default: 40
  111. },
  112. //底部滑块颜色
  113. bottomSlideColor: {
  114. type: String,
  115. default: '#2b94e7'
  116. },
  117. },
  118. data() {
  119. return {
  120. startPageX: 0, //开始距离
  121. moveLeft: 0, //滑动距离
  122. done: false, //是否成功
  123. autoLeft: 80, //验证滑块随机的像素
  124. autoTop: 80, //验证滑块随机的top像素
  125. isShow: false,
  126. width: 280 //主体宽度
  127. };
  128. },
  129. computed: {
  130. blockVerifyStyle() {
  131. return `top:${this.autoTop}px;left:${this.autoLeft}px;height:${this.slideSize}px;width:${this.slideSize}px;background-color:${this.slideColor};`
  132. },
  133. blockMoveStyle() {
  134. return `top:${this.autoTop}px;left:${this.moveLeft}px;height:${this.slideSize}px;width:${this.slideSize}px;background-color: ${this.slideColor};`
  135. },
  136. blockTouchStyle() {
  137. return `top:${this.autoTop}px;height:${this.slideSize}px;width:${this.slideSize}px;`
  138. },
  139. sliderMoveStyle() {
  140. return `left:${this.moveLeft}px;height:${this.bottomSlideSize}px;width:${this.bottomSlideSize}px;background-color: ${this.bottomSlideColor};`
  141. },
  142. sliderTouchStyle() {
  143. return `height:${this.bottomSlideSize}px;width:${this.bottomSlideSize}px;`
  144. }
  145. },
  146. methods: {
  147. // 拦截其他触摸事件防止nvue下input等元素层级问题
  148. stopMoveHandle(e) {
  149. if (e.preventDefault) {
  150. // 阻止页面滚动
  151. e.preventDefault()
  152. }
  153. },
  154. // 随机数
  155. rMathfloor(min, max) {
  156. //返回包括最大/小值
  157. return Math.floor(Math.random() * (max - min + 1)) + min;
  158. },
  159. // 初始化
  160. init() {
  161. this.moveLeft = 0;
  162. this.done = false;
  163. this.autoTop = this.rMathfloor(0, 170 - this.slideSize);
  164. this.autoLeft = this.rMathfloor(this.slideSize + 20, this.width - this.slideSize);
  165. },
  166. // 显示
  167. show() {
  168. this.isShow = true;
  169. this.init()
  170. },
  171. // 关闭
  172. hide() {
  173. this.closeHandle()
  174. },
  175. //按下
  176. touchstartHandle(e) {
  177. if (this.done) {
  178. return;
  179. }
  180. this.startPageX = e.changedTouches[0].pageX;
  181. },
  182. // 滑动
  183. touchmoveHandle(e) {
  184. // 滑动分两个块来操作不然会有数据抖动
  185. if (this.done) {
  186. return;
  187. }
  188. var left = e.changedTouches[0].pageX - this.startPageX; //补偿起始位置
  189. // 限制边界
  190. if (left < 0) {
  191. left = 0;
  192. };
  193. const maxLeft = this.width - this.slideSize;
  194. if (left > maxLeft) {
  195. left = maxLeft;
  196. };
  197. this.moveLeft = left;
  198. },
  199. // 滑动离开(最终)
  200. touchendHandle(e) {
  201. var endLeft = e.changedTouches[0].pageX;
  202. var verifyLeft = this.autoLeft + this.startPageX; //补偿起始位置
  203. var chazhi = verifyLeft - endLeft; //最终差值
  204. // 判断是否在正负差值区间
  205. if (chazhi >= 0 - this.between && chazhi <= this.between) {
  206. this.done = true;
  207. // 通过会执行成功和关闭
  208. this.closeHandle()
  209. this.$emit('success', '验证通过');
  210. this.$emit('close', '关闭');
  211. } else {
  212. this.$emit('error', '验证失败');
  213. // 失败会执行失败并重新初始化
  214. this.init();
  215. uni.showToast({
  216. title: this.tips,
  217. icon: 'none'
  218. });
  219. }
  220. },
  221. // 关闭事件
  222. closeHandle() {
  223. this.isShow = false
  224. this.$emit('close', '关闭');
  225. },
  226. // pc端
  227. mousedownHandle(e) {
  228. if (this.done) return;
  229. this.startPageX = e.pageX;
  230. this.isDragging = true;
  231. document.addEventListener('mousemove', this.mousemoveHandle);
  232. document.addEventListener('mouseup', this.mouseupHandle);
  233. },
  234. mousemoveHandle(e) {
  235. if (!this.isDragging || this.done) return;
  236. let left = e.pageX - this.startPageX;
  237. const maxLeft = this.width - this.slideSize;
  238. if (left < 0) left = 0;
  239. if (left > maxLeft) left = maxLeft;
  240. this.moveLeft = left;
  241. },
  242. mouseupHandle(e) {
  243. if (!this.isDragging) return;
  244. this.isDragging = false;
  245. document.removeEventListener('mousemove', this.mousemoveHandle);
  246. document.removeEventListener('mouseup', this.mouseupHandle);
  247. if (this.done) return;
  248. var endLeft = e.pageX;
  249. var verifyLeft = this.autoLeft + this.startPageX;
  250. var chazhi = verifyLeft - endLeft;
  251. if (chazhi >= -this.between && chazhi <= this.between) {
  252. this.done = true;
  253. this.closeHandle();
  254. this.$emit('success', '验证通过');
  255. this.$emit('close', '关闭');
  256. } else {
  257. this.$emit('error', '验证失败');
  258. uni.showToast({
  259. title: this.tips,
  260. icon: 'none'
  261. });
  262. this.init();
  263. }
  264. },
  265. }
  266. };
  267. </script>
  268. <style lang="scss" scoped>
  269. $sliderVerifyWidth: 280px;
  270. .zmm-slider-verify {
  271. position: fixed;
  272. top: 0;
  273. left: 0;
  274. right: 0;
  275. bottom: 0;
  276. z-index: 999;
  277. display: flex;
  278. flex-direction: row;
  279. align-items: center;
  280. justify-content: center;
  281. .zmm-slider-verify-mask {
  282. position: absolute;
  283. top: 0;
  284. left: 0;
  285. right: 0;
  286. bottom: 0;
  287. }
  288. .zmm-slider-verify-wrap {
  289. display: flex;
  290. flex-direction: column;
  291. position: relative;
  292. padding: 34rpx;
  293. border-radius: 24rpx;
  294. background-color: #ffffff;
  295. .zmm-slider-verify-top {
  296. display: flex;
  297. flex-direction: row;
  298. align-items: center;
  299. .zmm-slider-verify-title {
  300. flex: 1;
  301. color: #333;
  302. font-size: 32rpx;
  303. }
  304. .zmm-slider-verify-close {
  305. font-size: 28rpx;
  306. color: #333;
  307. }
  308. }
  309. .zmm-slider-verify-tips {
  310. margin-top: 12rpx;
  311. margin-bottom: 12rpx;
  312. .zmm-slider-verify-tips-text {
  313. color: #999;
  314. font-size: 28rpx;
  315. }
  316. }
  317. .zmm-slider-verify-box {
  318. position: relative;
  319. width: $sliderVerifyWidth;
  320. height: 170px;
  321. overflow: hidden;
  322. .zmm-slider-verify-img {
  323. width: $sliderVerifyWidth;
  324. height: 170px;
  325. border-radius: 8rpx;
  326. }
  327. .zmm-slider-verify-block-verify,
  328. .zmm-slider-verify-block-move,
  329. .zmm-slider-verify-block-touch {
  330. position: absolute;
  331. left: 0px;
  332. top: 0;
  333. border-radius: 8rpx;
  334. }
  335. .zmm-slider-verify-block-verify {
  336. border: 1px #fff solid;
  337. /* #ifndef APP-NVUE */
  338. box-sizing: border-box;
  339. /* #endif */
  340. }
  341. }
  342. .zmm-slider-verify-slider {
  343. margin-top: 34rpx;
  344. width: $sliderVerifyWidth;
  345. background-color: rgba(0, 0, 0, 0.07);
  346. position: relative;
  347. overflow: hidden;
  348. border-radius: 8rpx;
  349. border-radius: 750rpx;
  350. .zmm-slider-verify-slider-move,
  351. .zmm-slider-verify-slider-touch {
  352. border-radius: 750rpx;
  353. }
  354. .zmm-slider-verify-slider-move {
  355. position: absolute;
  356. left: 0px;
  357. top: 0px;
  358. z-index: 1;
  359. }
  360. .zmm-slider-verify-slider-touch {
  361. position: relative;
  362. z-index: 2;
  363. }
  364. }
  365. }
  366. }
  367. </style>