dragVerifyImgChip.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <template>
  2. <div class="drag-verify-container">
  3. <div :style="dragVerifyImgStyle">
  4. <img ref="checkImg" crossOrigin="anonymous" :src="imgsrc" @load="checkimgLoaded" style="width:100%" alt="">
  5. <canvas ref="maincanvas" class="main-canvas"></canvas>
  6. <canvas ref="movecanvas" :class="{goFirst:isOk, goKeep:isKeep}" class="move-canvas"></canvas>
  7. <div class="refresh" v-if="showRefresh && !isPassing">
  8. <i :class="refreshIcon" @click="refreshimg"></i>
  9. </div>
  10. <div class="tips success" v-if="showTips && isPassing">{{successTip}}</div>
  11. <div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{failTip}}</div>
  12. </div>
  13. <div ref="dragVerify" class="drag_verify" :style="dragVerifyStyle" @mousemove="dragMoving" @mouseup="dragFinish"
  14. @mouseleave="dragFinish" @touchmove.prevent="dragMoving" @touchend.prevent="dragFinish">
  15. <div class="dv_progress_bar" :class="{goFirst2:isOk}" ref="progressBar" :style="progressBarStyle">
  16. {{successMessage}}
  17. </div>
  18. <div class="dv_text" :style="textStyle" ref="message">
  19. {{message}}
  20. </div>
  21. <div class="dv_handler dv_handler_bg" :class="{goFirst:isOk}" @mousedown="dragStart" @touchstart.prevent="dragStart"
  22. ref="handler" :style="handlerStyle">
  23. <i :class="handlerIcon"></i>
  24. </div>
  25. </div>
  26. </div>
  27. </template>
  28. <script lang="ts">
  29. export default {
  30. name: "dragVerifyImgChip",
  31. props: {
  32. isPassing: {
  33. type: Boolean,
  34. default: false
  35. },
  36. width: {
  37. type: Number,
  38. default: 250
  39. },
  40. height: {
  41. type: Number,
  42. default: 40
  43. },
  44. text: {
  45. type: String,
  46. default: "swiping to the right side"
  47. },
  48. successText: {
  49. type: String,
  50. default: "success"
  51. },
  52. background: {
  53. type: String,
  54. default: "#eee"
  55. },
  56. progressBarBg: {
  57. type: String,
  58. default: "#76c61d"
  59. },
  60. completedBg: {
  61. type: String,
  62. default: "#76c61d"
  63. },
  64. circle: {
  65. type: Boolean,
  66. default: false
  67. },
  68. radius: {
  69. type: String,
  70. default: "4px"
  71. },
  72. handlerIcon: {
  73. type: String
  74. },
  75. successIcon: {
  76. type: String
  77. },
  78. handlerBg: {
  79. type: String,
  80. default: "#fff"
  81. },
  82. textSize: {
  83. type: String,
  84. default: "14px"
  85. },
  86. textColor: {
  87. type: String,
  88. default: "#333"
  89. },
  90. imgsrc: {
  91. type: String
  92. },
  93. barWidth: {
  94. type: Number,
  95. default: 40
  96. },
  97. barRadius: {
  98. type: Number,
  99. default: 8
  100. },
  101. showRefresh: {
  102. type: Boolean,
  103. default: false
  104. },
  105. refreshIcon: {
  106. type: String
  107. },
  108. showTips: {
  109. type: Boolean,
  110. default: true
  111. },
  112. successTip: {
  113. type: String,
  114. default: "验证通过,超过80%用户"
  115. },
  116. failTip: {
  117. type: String,
  118. default: "验证未通过,拖动滑块将悬浮图像正确合并"
  119. },
  120. diffWidth: {
  121. type: Number,
  122. default: 20
  123. }
  124. },
  125. mounted: function () {
  126. const dragEl = this.$refs.dragVerify;
  127. dragEl.style.setProperty("--textColor", this.textColor);
  128. dragEl.style.setProperty("--width", Math.floor(this.width / 2) + "px");
  129. dragEl.style.setProperty("--pwidth", -Math.floor(this.width / 2) + "px");
  130. },
  131. computed: {
  132. handlerStyle: function () {
  133. return {
  134. width: this.height + "px",
  135. height: this.height + "px",
  136. background: this.handlerBg
  137. };
  138. },
  139. message: function () {
  140. return this.isPassing ? "" : this.text;
  141. },
  142. successMessage: function () {
  143. return this.isPassing ? this.successText : "";
  144. },
  145. dragVerifyStyle: function () {
  146. return {
  147. width: this.width + "px",
  148. height: this.height + "px",
  149. lineHeight: this.height + "px",
  150. background: this.background,
  151. borderRadius: this.circle ? this.height / 2 + "px" : this.radius
  152. };
  153. },
  154. dragVerifyImgStyle: function () {
  155. return {
  156. width: this.width + "px",
  157. position: "relative",
  158. overflow: "hidden"
  159. };
  160. },
  161. progressBarStyle: function () {
  162. return {
  163. background: this.progressBarBg,
  164. height: this.height + "px",
  165. borderRadius: this.circle
  166. ? this.height / 2 + "px 0 0 " + this.height / 2 + "px"
  167. : this.radius
  168. };
  169. },
  170. textStyle: function () {
  171. return {
  172. height: this.height + "px",
  173. width: this.width + "px",
  174. fontSize: this.textSize
  175. };
  176. }
  177. },
  178. data() {
  179. return {
  180. isMoving: false,
  181. x: 0,
  182. isOk: false,
  183. isKeep: false,
  184. clipBarx: 0,
  185. showErrorTip: false
  186. };
  187. },
  188. methods: {
  189. draw: function (ctx, x, y, operation) {
  190. var l = this.barWidth;
  191. var r = this.barRadius;
  192. const PI = Math.PI;
  193. ctx.beginPath();
  194. ctx.moveTo(x, y);
  195. ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
  196. ctx.lineTo(x + l, y);
  197. ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
  198. ctx.lineTo(x + l, y + l);
  199. ctx.lineTo(x, y + l);
  200. ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
  201. ctx.lineTo(x, y);
  202. ctx.lineWidth = 2;
  203. ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
  204. ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
  205. ctx.stroke();
  206. ctx[operation]();
  207. ctx.globalCompositeOperation = "destination-over";
  208. },
  209. checkimgLoaded: function () {
  210. // 生成图片缺失位置
  211. var barWidth = this.barWidth;
  212. var imgHeight = this.$refs.checkImg.height;
  213. var imgWidth = this.$refs.checkImg.width;
  214. var halfWidth = Math.floor(this.width / 2);
  215. var refreshHeigth = 25;
  216. var tipHeight = 20;
  217. var x = halfWidth + Math.ceil(Math.random() * (halfWidth - barWidth - this.barRadius - 5));
  218. var y =
  219. refreshHeigth +
  220. Math.floor(
  221. Math.random() * (imgHeight - barWidth - refreshHeigth - tipHeight)
  222. );
  223. this.$refs.maincanvas.setAttribute("width", imgWidth);
  224. this.$refs.maincanvas.setAttribute("height", imgHeight);
  225. this.$refs.maincanvas.style.display = "block";
  226. var canvasCtx = this.$refs.maincanvas.getContext("2d");
  227. this.draw(canvasCtx, x, y, "fill");
  228. this.clipBarx = x;
  229. var moveCanvas = this.$refs.movecanvas;
  230. moveCanvas.setAttribute("width", imgWidth);
  231. moveCanvas.setAttribute("height", imgHeight);
  232. this.$refs.movecanvas.style.display = "block";
  233. const L = barWidth + this.barRadius * 2 + 3; //实际宽度
  234. var moveCtx = this.$refs.movecanvas.getContext("2d");
  235. moveCtx.clearRect(0, 0, imgWidth, imgHeight);
  236. this.draw(moveCtx, x, y, "clip");
  237. moveCtx.drawImage(this.$refs.checkImg, 0, 0, imgWidth, imgHeight);
  238. var y = y - this.barRadius * 2 - 1;
  239. const ImageData = moveCtx.getImageData(x, y, L, L);
  240. moveCanvas.setAttribute("width", L);
  241. moveCanvas.setAttribute("height", imgHeight);
  242. moveCtx.putImageData(ImageData, 0, y);
  243. },
  244. dragStart: function (e) {
  245. if (!this.isPassing) {
  246. this.isMoving = true;
  247. this.x =
  248. (e.pageX || e.touches[0].pageX)
  249. }
  250. this.showBar = true;
  251. this.showErrorTip = false;
  252. this.$emit("handlerMove");
  253. },
  254. dragMoving: function (e) {
  255. if (this.isMoving && !this.isPassing) {
  256. var _x = (e.pageX || e.touches[0].pageX) - this.x;
  257. var handler = this.$refs.handler;
  258. handler.style.left = _x + "px";
  259. this.$refs.progressBar.style.width = _x + this.height / 2 + "px";
  260. this.$refs.movecanvas.style.left = _x + "px";
  261. }
  262. },
  263. dragFinish: function (e) {
  264. if (this.isMoving && !this.isPassing) {
  265. var _x = (e.pageX || e.changedTouches[0].pageX) - this.x;
  266. if (Math.abs(_x - this.clipBarx) > this.diffWidth) {
  267. this.isOk = true;
  268. var that = this;
  269. setTimeout(function () {
  270. that.$refs.handler.style.left = "0";
  271. that.$refs.progressBar.style.width = "0";
  272. that.$refs.movecanvas.style.left = "0";
  273. that.isOk = false;
  274. }, 500);
  275. this.$emit("passfail");
  276. this.showErrorTip = true;
  277. } else {
  278. this.passVerify();
  279. }
  280. this.isMoving = false;
  281. }
  282. },
  283. passVerify: function () {
  284. this.$emit("update:isPassing", true);
  285. this.isMoving = false;
  286. var handler = this.$refs.handler;
  287. handler.children[0].className = this.successIcon;
  288. this.$refs.progressBar.style.background = this.completedBg;
  289. this.$refs.message.style["-webkit-text-fill-color"] = "unset";
  290. this.$refs.message.style.animation = "slidetounlock2 3s infinite";
  291. this.$refs.progressBar.style.color = "#fff";
  292. this.$refs.progressBar.style.fontSize = this.textSize;
  293. this.isKeep = true;
  294. setTimeout(() => {
  295. this.$refs.movecanvas.style.left = this.clipBarx + "px";
  296. setTimeout(() => {
  297. this.isKeep = false;
  298. this.$refs.maincanvas.style.display = "none";
  299. this.$refs.movecanvas.style.display = "none";
  300. }, 200);
  301. }, 100);
  302. this.$emit("passcallback");
  303. },
  304. reset: function () {
  305. this.reImg();
  306. this.checkimgLoaded();
  307. },
  308. reImg: function () {
  309. this.$emit("update:isPassing", false);
  310. const oriData = this.$options.data();
  311. for (const key in oriData) {
  312. if (Object.prototype.hasOwnProperty.call(oriData, key)) {
  313. this[key] = oriData[key]
  314. }
  315. }
  316. var handler = this.$refs.handler;
  317. var message = this.$refs.message;
  318. handler.style.left = "0";
  319. this.$refs.progressBar.style.width = "0";
  320. handler.children[0].className = this.handlerIcon;
  321. message.style["-webkit-text-fill-color"] = "transparent";
  322. message.style.animation = "slidetounlock 3s infinite";
  323. message.style.color = this.background;
  324. this.$refs.movecanvas.style.left = "0px";
  325. },
  326. refreshimg: function () {
  327. this.$emit("refresh");
  328. }
  329. },
  330. watch: {
  331. imgsrc: {
  332. immediate: false,
  333. handler: function () {
  334. this.reImg();
  335. }
  336. }
  337. }
  338. };
  339. </script>
  340. <style scoped>
  341. .drag_verify {
  342. position: relative;
  343. background-color: #e8e8e8;
  344. text-align: center;
  345. overflow: hidden;
  346. }
  347. .drag_verify .dv_handler {
  348. position: absolute;
  349. top: 0px;
  350. left: 0px;
  351. cursor: move;
  352. }
  353. .drag_verify .dv_handler i {
  354. color: #666;
  355. padding-left: 0;
  356. font-size: 16px;
  357. }
  358. .drag_verify .dv_handler .el-icon-circle-check {
  359. color: #6c6;
  360. margin-top: 9px;
  361. }
  362. .drag_verify .dv_progress_bar {
  363. position: absolute;
  364. height: 34px;
  365. width: 0px;
  366. }
  367. .drag_verify .dv_text {
  368. position: absolute;
  369. top: 0px;
  370. color: transparent;
  371. -moz-user-select: none;
  372. -webkit-user-select: none;
  373. user-select: none;
  374. -o-user-select: none;
  375. -ms-user-select: none;
  376. background: -webkit-gradient(linear,
  377. left top,
  378. right top,
  379. color-stop(0, var(--textColor)),
  380. color-stop(0.4, var(--textColor)),
  381. color-stop(0.5, #fff),
  382. color-stop(0.6, var(--textColor)),
  383. color-stop(1, var(--textColor)));
  384. -webkit-background-clip: text;
  385. -webkit-text-fill-color: transparent;
  386. -webkit-text-size-adjust: none;
  387. animation: slidetounlock 3s infinite;
  388. }
  389. .drag_verify .dv_text * {
  390. -webkit-text-fill-color: var(--textColor);
  391. }
  392. .goFirst {
  393. left: 0px !important;
  394. transition: left 0.5s;
  395. }
  396. .goKeep {
  397. transition: left 0.2s;
  398. }
  399. .goFirst2 {
  400. width: 0px !important;
  401. transition: width 0.5s;
  402. }
  403. .drag-verify-container {
  404. position: relative;
  405. line-height: 0;
  406. }
  407. .refresh {
  408. position: absolute;
  409. right: 5px;
  410. top: 5px;
  411. cursor: pointer;
  412. font-size: 20px;
  413. z-index: 200;
  414. }
  415. .tips {
  416. position: absolute;
  417. bottom: 0;
  418. height: 20px;
  419. line-height: 20px;
  420. text-align: center;
  421. width: 100%;
  422. font-size: 12px;
  423. z-index: 200;
  424. }
  425. .tips.success {
  426. background: rgba(255, 255, 255, 0.6);
  427. color: green;
  428. }
  429. .tips.danger {
  430. background: rgba(0, 0, 0, 0.6);
  431. color: yellow;
  432. }
  433. .main-canvas {
  434. width: 100%;
  435. height: 100%;
  436. position: absolute;
  437. top: 0;
  438. left: 0;
  439. }
  440. .move-canvas {
  441. position: absolute;
  442. top: 0;
  443. left: 0;
  444. }
  445. </style>
  446. <style>
  447. @-webkit-keyframes slidetounlock {
  448. 0% {
  449. background-position: var(--pwidth) 0;
  450. }
  451. 100% {
  452. background-position: var(--width) 0;
  453. }
  454. }
  455. @-webkit-keyframes slidetounlock2 {
  456. 0% {
  457. background-position: var(--pwidth) 0;
  458. }
  459. 100% {
  460. background-position: var(--pwidth) 0;
  461. }
  462. }
  463. </style>