123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- Vue.component('vue-puzzle-vcode', {
- props: {
- canvasWidth: { type: Number, default: 310 },
- canvasHeight: { type: Number, default: 160 },
-
- show: { type: Boolean, default: false },
- puzzleScale: { type: Number, default: 1 },
- sliderSize: { type: Number, default: 50 },
- range: { type: Number, default: 10 },
-
- imgs: {
- type: Array
- },
- successText: {
- type: String,
- default: "验证通过!"
- },
- failText: {
- type: String,
- default: "验证失败,请重试"
- },
- sliderText: {
- type: String,
- default: "拖动滑块完成拼图"
- }
- },
- data() {
- return {
- mouseDown: false,
- startWidth: 50,
- startX: 0,
- newX: 0,
- pinX: 0,
- pinY: 0,
- loading: false,
- isCanSlide: false,
- error: false,
- infoBoxShow: false,
- infoText: "",
- infoBoxFail: false,
- timer1: null,
- closeDown: false,
- isSuccess: false,
- imgIndex: -1,
- isSubmting: false,
- resetSvg: "el-icon-refresh",
- };
- },
- template: '<div :class="[\'vue-puzzle-vcode\', { show_: show }]"\n' +
- ' @mousedown="onCloseMouseDown"\n' +
- ' @mouseup="onCloseMouseUp"\n' +
- ' @touchstart="onCloseMouseDown"\n' +
- ' @touchend="onCloseMouseUp">\n' +
- ' <div class="vue-auth-box_"\n' +
- ' @mousedown.stop\n' +
- ' @touchstart.stop>\n' +
- ' <div class="auth-body_"\n' +
- ' :style="`height: ${canvasHeight}px`">\n' +
- ' <!-- 主图,有缺口 -->\n' +
- ' <canvas ref="canvas1"\n' +
- ' :width="canvasWidth"\n' +
- ' :height="canvasHeight"\n' +
- ' :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />\n' +
- ' <!-- 成功后显示的完整图 -->\n' +
- ' <canvas ref="canvas3"\n' +
- ' :class="[\'auth-canvas3_\', { show: isSuccess }]"\n' +
- ' :width="canvasWidth"\n' +
- ' :height="canvasHeight"\n' +
- ' :style="`width:${canvasWidth}px;height:${canvasHeight}px`" />\n' +
- ' <!-- 小图 -->\n' +
- ' <canvas :width="puzzleBaseSize"\n' +
- ' class="auth-canvas2_"\n' +
- ' :height="canvasHeight"\n' +
- ' ref="canvas2"\n' +
- ' :style="\n' +
- ' `width:${puzzleBaseSize}px;height:${canvasHeight}px;transform:translateX(${styleWidth -\n' +
- ' sliderBaseSize -\n' +
- ' (puzzleBaseSize - sliderBaseSize) *\n' +
- ' ((styleWidth - sliderBaseSize) /\n' +
- ' (canvasWidth - sliderBaseSize))}px)`\n' +
- ' " />\n' +
- ' <div :class="[\'loading-box_\', { hide_: !loading }]">\n' +
- ' <div class="loading-gif_">\n' +
- ' <span></span>\n' +
- ' <span></span>\n' +
- ' <span></span>\n' +
- ' <span></span>\n' +
- ' <span></span>\n' +
- ' </div>\n' +
- ' </div>\n' +
- ' <div :class="[\'info-box_\', { show: infoBoxShow }, { fail: infoBoxFail }]">\n' +
- ' {{ infoText }}\n' +
- ' </div>\n' +
- ' <div :class="[\'flash_\', { show: isSuccess }]"\n' +
- ' :style="\n' +
- ' `transform: translateX(${\n' +
- ' isSuccess\n' +
- ' ? `${canvasWidth + canvasHeight * 0.578}px`\n' +
- ' : `-${canvasHeight * 0.578}px`\n' +
- ' }) skew(-30deg, 0);`\n' +
- ' "></div>\n' +
- ' <i class="reset_" :class="resetSvg" @click="reset" />\n' +
- ' </div>\n' +
- ' <div class="auth-control_">\n' +
- ' <div class="range-box"\n' +
- ' :style="`height:${sliderBaseSize}px`">\n' +
- ' <div class="range-text">{{ sliderText }}</div>\n' +
- ' <div class="range-slider"\n' +
- ' ref="range-slider"\n' +
- ' :style="`width:${styleWidth}px`">\n' +
- ' <div :class="[\'range-btn\', { isDown: mouseDown }]"\n' +
- ' :style="`width:${sliderBaseSize}px`"\n' +
- ' @mousedown="onRangeMouseDown($event)"\n' +
- ' @touchstart="onRangeMouseDown($event)">\n' +
- ' <div></div>\n' +
- ' <div></div>\n' +
- ' <div></div>\n' +
- ' </div>\n' +
- ' </div>\n' +
- ' </div>\n' +
- ' </div>\n' +
- ' </div>\n' +
- ' </div>',
- mounted: function () {
- document.body.appendChild(this.$el);
- document.addEventListener("mousemove", this.onRangeMouseMove, false);
- document.addEventListener("mouseup", this.onRangeMouseUp, false);
- document.addEventListener("touchmove", this.onRangeMouseMove, {
- passive: false
- });
- document.addEventListener("touchend", this.onRangeMouseUp, false);
- if (this.show) {
- document.body.classList.add("vue-puzzle-overflow");
- this.reset();
- }
- },
- beforeDestroy() {
- clearTimeout(this.timer1);
- document.body.removeChild(this.$el);
- document.removeEventListener("mousemove", this.onRangeMouseMove, false);
- document.removeEventListener("mouseup", this.onRangeMouseUp, false);
- document.removeEventListener("touchmove", this.onRangeMouseMove, {
- passive: false
- });
- document.removeEventListener("touchend", this.onRangeMouseUp, false);
- },
-
- watch: {
- show(newV) {
-
- if (newV) {
- document.body.classList.add("vue-puzzle-overflow");
- this.reset();
- } else {
- this.isSubmting = false;
- this.isSuccess = false;
- this.infoBoxShow = false;
- document.body.classList.remove("vue-puzzle-overflow");
- }
- }
- },
-
- computed: {
-
- styleWidth() {
- const w = this.startWidth + this.newX - this.startX;
- return w < this.sliderBaseSize
- ? this.sliderBaseSize
- : w > this.canvasWidth
- ? this.canvasWidth
- : w;
- },
-
- puzzleBaseSize() {
- return Math.round(
- Math.max(Math.min(this.puzzleScale, 2), 0.2) * 52.5 + 6
- );
- },
-
- sliderBaseSize() {
- return Math.max(
- Math.min(
- Math.round(this.sliderSize),
- Math.round(this.canvasWidth * 0.5)
- ),
- 10
- );
- }
- },
-
- methods: {
-
- onClose() {
- if (!this.mouseDown && !this.isSubmting) {
- clearTimeout(this.timer1);
- this.$emit("close");
- }
- },
- onCloseMouseDown() {
- this.closeDown = true;
- },
- onCloseMouseUp() {
- if (this.closeDown) {
- this.onClose();
- }
- this.closeDown = false;
- },
-
- onRangeMouseDown(e) {
- if (this.isCanSlide) {
- this.mouseDown = true;
- this.startWidth = this.$refs["range-slider"].clientWidth;
- this.newX = e.clientX || e.changedTouches[0].clientX;
- this.startX = e.clientX || e.changedTouches[0].clientX;
- }
- },
-
- onRangeMouseMove(e) {
- if (this.mouseDown) {
- e.preventDefault();
- this.newX = e.clientX || e.changedTouches[0].clientX;
- }
- },
-
- onRangeMouseUp() {
- if (this.mouseDown) {
- this.mouseDown = false;
- this.submit();
- }
- },
-
- init(withCanvas) {
-
- if(this.loading && !withCanvas){
- return;
- }
- this.loading = true;
- this.isCanSlide = false;
- const c = this.$refs.canvas1;
- const c2 = this.$refs.canvas2;
- const c3 = this.$refs.canvas3;
- const ctx = c.getContext("2d");
- const ctx2 = c2.getContext("2d");
- const ctx3 = c3.getContext("2d");
- const isFirefox = navigator.userAgent.indexOf("Firefox") >= 0 && navigator.userAgent.indexOf("Windows") >= 0;
- const img = document.createElement("img");
- ctx.fillStyle = "rgba(255,255,255,1)";
- ctx3.fillStyle = "rgba(255,255,255,1)";
- ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
- ctx2.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
-
- this.pinX = this.getRandom(this.puzzleBaseSize,this.canvasWidth - this.puzzleBaseSize - 20);
- this.pinY = this.getRandom(20,this.canvasHeight - this.puzzleBaseSize - 20);
- img.crossOrigin = "anonymous";
- img.onload = () => {
- const [x, y, w, h] = this.makeImgSize(img);
- ctx.save();
-
- this.paintBrick(ctx);
- ctx.closePath();
- if(!isFirefox){
- ctx.shadowOffsetX = 0;
- ctx.shadowOffsetY = 0;
- ctx.shadowColor = "#000";
- ctx.shadowBlur = 3;
- ctx.fill();
- ctx.clip();
- } else {
- ctx.clip();
- ctx.save();
- ctx.shadowOffsetX = 0;
- ctx.shadowOffsetY = 0;
- ctx.shadowColor = "#000";
- ctx.shadowBlur = 3;
- ctx.fill();
- ctx.restore();
- }
- ctx.drawImage(img, x, y, w, h);
- ctx3.fillRect(0,0,this.canvasWidth,this.canvasHeight);
- ctx3.drawImage(img, x, y, w, h);
-
- ctx.globalCompositeOperation = "source-atop";
- this.paintBrick(ctx);
- ctx.arc(
- this.pinX + Math.ceil(this.puzzleBaseSize / 2),
- this.pinY + Math.ceil(this.puzzleBaseSize / 2),
- this.puzzleBaseSize * 1.2,
- 0,
- Math.PI * 2,
- true
- );
- ctx.closePath();
- ctx.shadowColor = "rgba(255, 255, 255, .8)";
- ctx.shadowOffsetX = -1;
- ctx.shadowOffsetY = -1;
- ctx.shadowBlur = Math.min(Math.ceil(8 * this.puzzleScale), 12);
- ctx.fillStyle = "#ffffaa";
- ctx.fill();
-
- const imgData = ctx.getImageData(
- this.pinX - 3,
- this.pinY - 20,
- this.pinX + this.puzzleBaseSize + 5,
- this.pinY + this.puzzleBaseSize + 5
- );
- ctx2.putImageData(imgData, 0, this.pinY - 20);
-
-
-
- ctx.restore();
- ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
-
- ctx.save();
- this.paintBrick(ctx);
- ctx.globalAlpha = 0.8;
- ctx.fillStyle = "#ffffff";
- ctx.fill();
- ctx.restore();
-
- ctx.save();
- ctx.globalCompositeOperation = "source-atop";
- this.paintBrick(ctx);
- ctx.arc(
- this.pinX + Math.ceil(this.puzzleBaseSize / 2),
- this.pinY + Math.ceil(this.puzzleBaseSize / 2),
- this.puzzleBaseSize * 1.2,
- 0,
- Math.PI * 2,
- true
- );
- ctx.shadowColor = "#000";
- ctx.shadowOffsetX = 2;
- ctx.shadowOffsetY = 2;
- ctx.shadowBlur = 16;
- ctx.fill();
- ctx.restore();
-
- ctx.save();
- ctx.globalCompositeOperation = "destination-over";
- ctx.drawImage(img, x, y, w, h);
- ctx.restore();
- this.loading = false;
- this.isCanSlide = true;
- };
- img.onerror = () => {
- this.init(true);
- };
- if (!withCanvas && this.imgs && this.imgs.length) {
- let randomNum = this.getRandom(0, this.imgs.length - 1);
- if (randomNum === this.imgIndex) {
- if (randomNum === this.imgs.length - 1) {
- randomNum = 0;
- } else {
- randomNum++;
- }
- }
- this.imgIndex = randomNum;
- img.src = this.imgs[randomNum];
- } else {
- img.src = this.makeImgWithCanvas();
- }
- },
-
- getRandom(min, max) {
- return Math.ceil(Math.random() * (max - min) + min);
- },
-
- makeImgSize(img) {
- const imgScale = img.width / img.height;
- const canvasScale = this.canvasWidth / this.canvasHeight;
- let x = 0,
- y = 0,
- w = 0,
- h = 0;
- if (imgScale > canvasScale) {
- h = this.canvasHeight;
- w = imgScale * h;
- y = 0;
- x = (this.canvasWidth - w) / 2;
- } else {
- w = this.canvasWidth;
- h = w / imgScale;
- x = 0;
- y = (this.canvasHeight - h) / 2;
- }
- return [x, y, w, h];
- },
-
- paintBrick(ctx) {
- const moveL = Math.ceil(15 * this.puzzleScale);
- ctx.beginPath();
- ctx.moveTo(this.pinX, this.pinY);
- ctx.lineTo(this.pinX + moveL, this.pinY);
- ctx.arcTo(
- this.pinX + moveL,
- this.pinY - moveL / 2,
- this.pinX + moveL + moveL / 2,
- this.pinY - moveL / 2,
- moveL / 2
- );
- ctx.arcTo(
- this.pinX + moveL + moveL,
- this.pinY - moveL / 2,
- this.pinX + moveL + moveL,
- this.pinY,
- moveL / 2
- );
- ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY);
- ctx.lineTo(this.pinX + moveL + moveL + moveL, this.pinY + moveL);
- ctx.arcTo(
- this.pinX + moveL + moveL + moveL + moveL / 2,
- this.pinY + moveL,
- this.pinX + moveL + moveL + moveL + moveL / 2,
- this.pinY + moveL + moveL / 2,
- moveL / 2
- );
- ctx.arcTo(
- this.pinX + moveL + moveL + moveL + moveL / 2,
- this.pinY + moveL + moveL,
- this.pinX + moveL + moveL + moveL,
- this.pinY + moveL + moveL,
- moveL / 2
- );
- ctx.lineTo(
- this.pinX + moveL + moveL + moveL,
- this.pinY + moveL + moveL + moveL
- );
- ctx.lineTo(this.pinX, this.pinY + moveL + moveL + moveL);
- ctx.lineTo(this.pinX, this.pinY + moveL + moveL);
- ctx.arcTo(
- this.pinX + moveL / 2,
- this.pinY + moveL + moveL,
- this.pinX + moveL / 2,
- this.pinY + moveL + moveL / 2,
- moveL / 2
- );
- ctx.arcTo(
- this.pinX + moveL / 2,
- this.pinY + moveL,
- this.pinX,
- this.pinY + moveL,
- moveL / 2
- );
- ctx.lineTo(this.pinX, this.pinY);
- },
-
- makeImgWithCanvas() {
- const canvas = document.createElement("canvas");
- const ctx = canvas.getContext("2d");
- canvas.width = this.canvasWidth;
- canvas.height = this.canvasHeight;
- ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
- 100,
- 255
- )},${this.getRandom(100, 255)})`;
- ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
-
- for (let i = 0; i < 12; i++) {
- ctx.fillStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
- 100,
- 255
- )},${this.getRandom(100, 255)})`;
- ctx.strokeStyle = `rgb(${this.getRandom(100, 255)},${this.getRandom(
- 100,
- 255
- )},${this.getRandom(100, 255)})`;
- if (this.getRandom(0, 2) > 1) {
-
- ctx.save();
- ctx.rotate((this.getRandom(-90, 90) * Math.PI) / 180);
- ctx.fillRect(
- this.getRandom(-20, canvas.width - 20),
- this.getRandom(-20, canvas.height - 20),
- this.getRandom(10, canvas.width / 2 + 10),
- this.getRandom(10, canvas.height / 2 + 10)
- );
- ctx.restore();
- } else {
-
- ctx.beginPath();
- const ran = this.getRandom(-Math.PI, Math.PI);
- ctx.arc(
- this.getRandom(0, canvas.width),
- this.getRandom(0, canvas.height),
- this.getRandom(10, canvas.height / 2 + 10),
- ran,
- ran + Math.PI * 1.5
- );
- ctx.closePath();
- ctx.fill();
- }
- }
- return canvas.toDataURL("image/png");
- },
-
- submit() {
- this.isSubmting = true;
-
-
- const x = Math.abs(
- this.pinX -
- (this.styleWidth - this.sliderBaseSize) +
- (this.puzzleBaseSize - this.sliderBaseSize) *
- ((this.styleWidth - this.sliderBaseSize) /
- (this.canvasWidth - this.sliderBaseSize)) -
- 3
- );
- if (x < this.range) {
-
- this.infoText = this.successText;
- this.infoBoxFail = false;
- this.infoBoxShow = true;
- this.isCanSlide = false;
- this.isSuccess = true;
-
- clearTimeout(this.timer1);
- this.timer1 = setTimeout(() => {
-
- this.isSubmting = false;
- this.$emit("success", x);
- }, 800);
- } else {
-
- this.infoText = this.failText;
- this.infoBoxFail = true;
- this.infoBoxShow = true;
- this.isCanSlide = false;
-
- this.$emit("fail", x);
-
- clearTimeout(this.timer1);
- this.timer1 = setTimeout(() => {
- this.isSubmting = false;
- this.reset();
- }, 800);
- }
- },
-
- resetState() {
- this.infoBoxFail = false;
- this.infoBoxShow = false;
- this.isCanSlide = false;
- this.isSuccess = false;
- this.startWidth = this.sliderBaseSize;
- this.startX = 0;
- this.newX = 0;
- },
-
- reset() {
- if(this.isSubmting){
- return;
- }
- this.resetState();
- this.init();
- }
- }
- });
|