|
|
@@ -1,15 +1,24 @@
|
|
|
<template>
|
|
|
<div class="cap-wrap" @click="refresh" :title="'点击刷新验证码'">
|
|
|
- <canvas ref="cv" :width="w" :height="h"></canvas>
|
|
|
+ <canvas ref="cv"></canvas>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
export default {
|
|
|
name: "CaptchaCanvas",
|
|
|
- props: { value: String, w: { type: Number, default: 173 }, h: { type: Number, default: 64 } },
|
|
|
+ props: { value: String },
|
|
|
data() { return { code: "" }; },
|
|
|
- mounted() { this.refresh(); },
|
|
|
+ mounted() {
|
|
|
+ this.refresh();
|
|
|
+ if (typeof ResizeObserver !== "undefined") {
|
|
|
+ this.ro = new ResizeObserver(() => this.draw());
|
|
|
+ this.ro.observe(this.$el);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.ro) { this.ro.disconnect(); this.ro = null; }
|
|
|
+ },
|
|
|
methods: {
|
|
|
randChar() {
|
|
|
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
|
@@ -18,37 +27,53 @@ export default {
|
|
|
refresh() {
|
|
|
this.code = Array.from({ length: 4 }).map(() => this.randChar()).join("");
|
|
|
this.$emit("input", this.code);
|
|
|
+ this.draw();
|
|
|
+ },
|
|
|
+ draw() {
|
|
|
const cv = this.$refs.cv;
|
|
|
+ if (!cv) return;
|
|
|
+ const wrap = this.$el;
|
|
|
+ const w = wrap.clientWidth || 120;
|
|
|
+ const h = wrap.clientHeight || 60;
|
|
|
+ const dpr = window.devicePixelRatio || 1;
|
|
|
+ cv.width = Math.round(w * dpr);
|
|
|
+ cv.height = Math.round(h * dpr);
|
|
|
+ cv.style.width = w + "px";
|
|
|
+ cv.style.height = h + "px";
|
|
|
const ctx = cv.getContext("2d");
|
|
|
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
|
|
// 背景
|
|
|
- ctx.clearRect(0, 0, this.w, this.h);
|
|
|
- const g = ctx.createLinearGradient(0, 0, this.w, this.h);
|
|
|
+ ctx.clearRect(0, 0, w, h);
|
|
|
+ const g = ctx.createLinearGradient(0, 0, w, h);
|
|
|
g.addColorStop(0, "rgba(14,45,120,0.65)");
|
|
|
g.addColorStop(1, "rgba(8,20,55,0.85)");
|
|
|
ctx.fillStyle = g;
|
|
|
- ctx.fillRect(0, 0, this.w, this.h);
|
|
|
+ ctx.fillRect(0, 0, w, h);
|
|
|
|
|
|
// 干扰线
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
- ctx.strokeStyle = `rgba(60,180,255,${0.15 + Math.random()*0.25})`;
|
|
|
+ ctx.strokeStyle = `rgba(60,180,255,${0.15 + Math.random() * 0.25})`;
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(Math.random() * this.w, Math.random() * this.h);
|
|
|
- ctx.lineTo(Math.random() * this.w, Math.random() * this.h);
|
|
|
+ ctx.moveTo(Math.random() * w, Math.random() * h);
|
|
|
+ ctx.lineTo(Math.random() * w, Math.random() * h);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
|
|
|
- // 字符
|
|
|
- ctx.font = "bold 22px Arial";
|
|
|
+ // 字符:按容器宽高等分布点 + 字号随高度缩放
|
|
|
+ const n = this.code.length;
|
|
|
+ const fontSize = Math.max(12, Math.round(h * 0.55));
|
|
|
+ ctx.font = `bold ${fontSize}px Arial`;
|
|
|
ctx.textBaseline = "middle";
|
|
|
- for (let i = 0; i < this.code.length; i++) {
|
|
|
- const x = 22 + i * 32;
|
|
|
- const y = this.h / 2 + (Math.random() * 6 - 4);
|
|
|
+ ctx.textAlign = "center";
|
|
|
+ for (let i = 0; i < n; i++) {
|
|
|
+ const x = (w * (i + 0.5)) / n;
|
|
|
+ const y = h / 2 + (Math.random() * 6 - 4);
|
|
|
ctx.save();
|
|
|
ctx.translate(x, y);
|
|
|
- ctx.rotate((Math.random() * 14 - 7) * Math.PI / 180);
|
|
|
+ ctx.rotate(((Math.random() * 14 - 7) * Math.PI) / 180);
|
|
|
ctx.fillStyle = "rgba(210,245,255,0.92)";
|
|
|
- ctx.fillText(this.code[i], -8, 0);
|
|
|
+ ctx.fillText(this.code[i], 0, 0);
|
|
|
ctx.restore();
|
|
|
}
|
|
|
}
|
|
|
@@ -57,13 +82,14 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.cap-wrap{
|
|
|
- width: 165px;
|
|
|
- height: 54px;
|
|
|
+.cap-wrap {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
border: 1px solid #3D6EB8;
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
cursor: pointer;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
-canvas{ display:block; }
|
|
|
-</style>
|
|
|
+canvas { display: block; }
|
|
|
+</style>
|