index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import { reactive, ref, defineComponent, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, createCommentVNode, toDisplayString } from "vue";
  2. const PI = Math.PI;
  3. function sum(x, y) {
  4. return x + y;
  5. }
  6. function square(x) {
  7. return x * x;
  8. }
  9. function draw(ctx, x, y, l, r, operation) {
  10. ctx.beginPath();
  11. ctx.moveTo(x, y);
  12. ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
  13. ctx.lineTo(x + l, y);
  14. ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
  15. ctx.lineTo(x + l, y + l);
  16. ctx.lineTo(x, y + l);
  17. ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
  18. ctx.lineTo(x, y);
  19. ctx.lineWidth = 2;
  20. ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
  21. ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
  22. ctx.stroke();
  23. ctx[operation]();
  24. ctx.globalCompositeOperation = "destination-over";
  25. }
  26. function createImg(imgs, onload) {
  27. const img = document.createElement("img");
  28. img.crossOrigin = "Anonymous";
  29. img.onload = onload;
  30. img.onerror = () => {
  31. img.src = getRandomImg(imgs);
  32. };
  33. img.src = getRandomImg(imgs);
  34. return img;
  35. }
  36. function getRandomNumberByRange(start, end) {
  37. return Math.round(Math.random() * (end - start) + start);
  38. }
  39. function getRandomImg(imgs) {
  40. const len = imgs.length;
  41. return len > 0 ? imgs[getRandomNumberByRange(0, len - 1)] : "https://picsum.photos/300/150?image=" + getRandomNumberByRange(0, 1084);
  42. }
  43. function throttle(fn, interval, options = { leading: true, trailing: true }) {
  44. const { leading, trailing, resultCallback } = options;
  45. let lastTime = 0;
  46. let timer = null;
  47. const _throttle = function(...args) {
  48. return new Promise((resolve, reject) => {
  49. const nowTime = new Date().getTime();
  50. if (!lastTime && !leading)
  51. lastTime = nowTime;
  52. const remainTime = interval - (nowTime - lastTime);
  53. if (remainTime <= 0) {
  54. if (timer) {
  55. clearTimeout(timer);
  56. timer = null;
  57. }
  58. const result = fn.apply(this, args);
  59. if (resultCallback)
  60. resultCallback(result);
  61. resolve(result);
  62. lastTime = nowTime;
  63. return;
  64. }
  65. if (trailing && !timer) {
  66. timer = setTimeout(() => {
  67. timer = null;
  68. lastTime = !leading ? 0 : new Date().getTime();
  69. const result = fn.apply(this, args);
  70. if (resultCallback)
  71. resultCallback(result);
  72. resolve(result);
  73. }, remainTime);
  74. }
  75. });
  76. };
  77. _throttle.cancel = function() {
  78. if (timer)
  79. clearTimeout(timer);
  80. timer = null;
  81. lastTime = 0;
  82. };
  83. return _throttle;
  84. }
  85. function useSlideAction() {
  86. const origin = reactive({
  87. x: 0,
  88. y: 0
  89. });
  90. const success = ref(false);
  91. const isMouseDown = ref(false);
  92. const timestamp = ref(0);
  93. const trail = ref([]);
  94. const start = (e) => {
  95. if (success.value)
  96. return;
  97. if (e instanceof MouseEvent) {
  98. origin.x = e.clientX;
  99. origin.y = e.clientY;
  100. } else {
  101. origin.x = e.changedTouches[0].pageX;
  102. origin.y = e.changedTouches[0].pageY;
  103. }
  104. isMouseDown.value = true;
  105. timestamp.value = Date.now();
  106. };
  107. const move = (w, e, cb) => {
  108. if (!isMouseDown.value)
  109. return false;
  110. let moveX = 0;
  111. let moveY = 0;
  112. if (e instanceof MouseEvent) {
  113. moveX = e.clientX - origin.x;
  114. moveY = e.clientY - origin.y;
  115. } else {
  116. moveX = e.changedTouches[0].pageX - origin.x;
  117. moveY = e.changedTouches[0].pageY - origin.y;
  118. }
  119. if (moveX < 0 || moveX + 38 >= w)
  120. return false;
  121. cb(moveX);
  122. trail.value.push(moveY);
  123. };
  124. const verify = (left, blockX, accuracy) => {
  125. const arr = trail.value;
  126. const average = arr.reduce(sum) / arr.length;
  127. const deviations = arr.map((x) => x - average);
  128. const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
  129. const leftNum = parseInt(left);
  130. accuracy = accuracy <= 1 ? 1 : accuracy > 10 ? 10 : accuracy;
  131. return {
  132. spliced: Math.abs(leftNum - blockX) <= accuracy,
  133. TuringTest: average !== stddev
  134. };
  135. };
  136. const end = (e, cb) => {
  137. if (!isMouseDown.value)
  138. return false;
  139. isMouseDown.value = false;
  140. const moveX = e instanceof MouseEvent ? e.clientX : e.changedTouches[0].pageX;
  141. if (moveX === origin.x)
  142. return false;
  143. timestamp.value = Date.now() - timestamp.value;
  144. cb(timestamp.value);
  145. };
  146. return { origin, success, isMouseDown, timestamp, trail, start, move, end, verify };
  147. }
  148. var slideVerify_vue_vue_type_style_index_0_scoped_true_lang = "";
  149. var _export_sfc = (sfc, props) => {
  150. const target = sfc.__vccOpts || sfc;
  151. for (const [key, val] of props) {
  152. target[key] = val;
  153. }
  154. return target;
  155. };
  156. const _sfc_main = defineComponent({
  157. name: "SlideVerify",
  158. props: {
  159. l: {
  160. type: Number,
  161. default: 42
  162. },
  163. r: {
  164. type: Number,
  165. default: 10
  166. },
  167. w: {
  168. type: Number,
  169. default: 310
  170. },
  171. h: {
  172. type: Number,
  173. default: 155
  174. },
  175. sliderText: {
  176. type: String,
  177. default: "Slide filled right"
  178. },
  179. accuracy: {
  180. type: Number,
  181. default: 5
  182. },
  183. show: {
  184. type: Boolean,
  185. default: true
  186. },
  187. imgs: {
  188. type: Array,
  189. default: () => []
  190. },
  191. interval: {
  192. type: Number,
  193. default: 50
  194. }
  195. },
  196. emits: ["success", "again", "fail", "refresh", "imageLoad"],
  197. setup(props, { emit }) {
  198. const { imgs, l, r, w, h, accuracy, interval } = props;
  199. const loadBlock = ref(true);
  200. const blockX = ref(0);
  201. const blockY = ref(0);
  202. const containerCls = reactive({
  203. containerActive: false,
  204. containerSuccess: false,
  205. containerFail: false
  206. });
  207. const sliderBox = reactive({
  208. iconCls: "arrow-right",
  209. width: "0",
  210. left: "0"
  211. });
  212. const block = ref();
  213. const blockCtx = ref();
  214. const canvas = ref();
  215. const canvasCtx = ref();
  216. let img;
  217. const { success, start, move, end, verify } = useSlideAction();
  218. const reset = () => {
  219. var _a, _b;
  220. success.value = false;
  221. containerCls.containerActive = false;
  222. containerCls.containerSuccess = false;
  223. containerCls.containerFail = false;
  224. sliderBox.iconCls = "arrow-right";
  225. sliderBox.left = "0";
  226. sliderBox.width = "0";
  227. block.value.style.left = "0";
  228. (_a = canvasCtx.value) == null ? void 0 : _a.clearRect(0, 0, w, h);
  229. (_b = blockCtx.value) == null ? void 0 : _b.clearRect(0, 0, w, h);
  230. block.value.width = w;
  231. img.src = getRandomImg(imgs);
  232. };
  233. const refresh = () => {
  234. reset();
  235. emit("refresh");
  236. };
  237. function moveCb(moveX) {
  238. sliderBox.left = moveX + "px";
  239. let blockLeft = (w - 40 - 20) / (w - 40) * moveX;
  240. block.value.style.left = blockLeft + "px";
  241. containerCls.containerActive = true;
  242. sliderBox.width = moveX + "px";
  243. }
  244. function endCb(timestamp) {
  245. const { spliced, TuringTest } = verify(block.value.style.left, blockX.value, accuracy);
  246. if (spliced) {
  247. if (accuracy === -1) {
  248. containerCls.containerSuccess = true;
  249. sliderBox.iconCls = "success";
  250. success.value = true;
  251. emit("success", timestamp);
  252. return;
  253. }
  254. if (TuringTest) {
  255. containerCls.containerSuccess = true;
  256. sliderBox.iconCls = "success";
  257. success.value = true;
  258. emit("success", timestamp);
  259. } else {
  260. containerCls.containerFail = true;
  261. sliderBox.iconCls = "fail";
  262. emit("again");
  263. }
  264. } else {
  265. containerCls.containerFail = true;
  266. sliderBox.iconCls = "fail";
  267. emit("fail");
  268. }
  269. }
  270. const touchMoveEvent = throttle((e) => {
  271. move(w, e, moveCb);
  272. }, interval);
  273. const touchEndEvent = (e) => {
  274. end(e, endCb);
  275. };
  276. onMounted(() => {
  277. var _a, _b;
  278. const _canvasCtx = (_a = canvas.value) == null ? void 0 : _a.getContext("2d");
  279. const _blockCtx = (_b = block.value) == null ? void 0 : _b.getContext("2d", { willReadFrequently: true });
  280. canvasCtx.value = _canvasCtx;
  281. blockCtx.value = _blockCtx;
  282. img = createImg(imgs, () => {
  283. loadBlock.value = false;
  284. const L = l + r * 2 + 3;
  285. blockX.value = getRandomNumberByRange(L + 10, w - (L + 10));
  286. blockY.value = getRandomNumberByRange(10 + r * 2, h - (L + 10));
  287. if (_canvasCtx && _blockCtx) {
  288. draw(_canvasCtx, blockX.value, blockY.value, l, r, "fill");
  289. draw(_blockCtx, blockX.value, blockY.value, l, r, "clip");
  290. _canvasCtx.drawImage(img, 0, 0, w, h);
  291. _blockCtx.drawImage(img, 0, 0, w, h);
  292. const _y = blockY.value - r * 2 - 1;
  293. const imgData = _blockCtx.getImageData(blockX.value, _y, L, L);
  294. block.value.width = L;
  295. _blockCtx.putImageData(imgData, 0, _y);
  296. }
  297. emit('imageLoad')
  298. });
  299. document.addEventListener("mousemove", touchMoveEvent);
  300. document.addEventListener("mouseup", touchEndEvent);
  301. });
  302. onBeforeUnmount(() => {
  303. document.removeEventListener("mousemove", touchMoveEvent);
  304. document.removeEventListener("mouseup", touchEndEvent);
  305. });
  306. return {
  307. block,
  308. canvas,
  309. loadBlock,
  310. containerCls,
  311. sliderBox,
  312. refresh,
  313. sliderDown: start,
  314. touchStartEvent: start,
  315. touchMoveEvent,
  316. touchEndEvent
  317. };
  318. }
  319. });
  320. const _hoisted_1 = ["width", "height"];
  321. const _hoisted_2 = ["width", "height"];
  322. const _hoisted_3 = { class: "slide-verify-slider-text" };
  323. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  324. return openBlock(), createElementBlock("div", {
  325. id: "slideVerify",
  326. class: "slide-verify",
  327. style: normalizeStyle({ width: _ctx.w + "px" }),
  328. onselectstart: "return false;"
  329. }, [
  330. createElementVNode("div", {
  331. class: normalizeClass({ "slider-verify-loading": _ctx.loadBlock })
  332. }, null, 2),
  333. createElementVNode("canvas", {
  334. ref: "canvas",
  335. width: _ctx.w,
  336. height: _ctx.h
  337. }, null, 8, _hoisted_1),
  338. _ctx.show ? (openBlock(), createElementBlock("div", {
  339. key: 0,
  340. class: "slide-verify-refresh-icon",
  341. onClick: _cache[0] || (_cache[0] = (...args) => _ctx.refresh && _ctx.refresh(...args))
  342. }, _cache[5] || (_cache[5] = [
  343. createElementVNode("i", { class: "iconfont icon-refresh" }, null, -1)
  344. ]))) : createCommentVNode("", true),
  345. createElementVNode("canvas", {
  346. ref: "block",
  347. width: _ctx.w,
  348. height: _ctx.h,
  349. class: "slide-verify-block"
  350. }, null, 8, _hoisted_2),
  351. createElementVNode("div", {
  352. class: normalizeClass(["slide-verify-slider", {
  353. "container-active": _ctx.containerCls.containerActive,
  354. "container-success": _ctx.containerCls.containerSuccess,
  355. "container-fail": _ctx.containerCls.containerFail
  356. }])
  357. }, [
  358. createElementVNode("div", {
  359. class: "slide-verify-slider-mask",
  360. style: normalizeStyle({ width: _ctx.sliderBox.width })
  361. }, [
  362. createElementVNode("div", {
  363. class: "slide-verify-slider-mask-item",
  364. style: normalizeStyle({ left: _ctx.sliderBox.left }),
  365. onMousedown: _cache[1] || (_cache[1] = (...args) => _ctx.sliderDown && _ctx.sliderDown(...args)),
  366. onTouchstart: _cache[2] || (_cache[2] = (...args) => _ctx.touchStartEvent && _ctx.touchStartEvent(...args)),
  367. onTouchmove: _cache[3] || (_cache[3] = (...args) => _ctx.touchMoveEvent && _ctx.touchMoveEvent(...args)),
  368. onTouchend: _cache[4] || (_cache[4] = (...args) => _ctx.touchEndEvent && _ctx.touchEndEvent(...args))
  369. }, [
  370. createElementVNode("i", {
  371. class: normalizeClass(["slide-verify-slider-mask-item-icon", "iconfont", `icon-${_ctx.sliderBox.iconCls}`])
  372. }, null, 2)
  373. ], 36)
  374. ], 4),
  375. createElementVNode("span", _hoisted_3, toDisplayString(_ctx.sliderText), 1)
  376. ], 2)
  377. ], 4);
  378. }
  379. var SlideVerify = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-3f647794"]]);
  380. export { SlideVerify as default };