比例圆
这期的需求是要做一个比例圆,首先的想到的是canvas,以此为基础画了一个比例圆,主要是处理从90°作为起点,以及弧度和角度之间的转换。话不多说,直接上代码!
<template>
<canvas ref="ratePie" id="ratePie" width="16px" height="16px"></canvas>
</template>
<script>
export default {
props: {
rate: {
type: [Number, String],
default: 10,
},
percentage: {
type: [Number, String],
default: 100,
}
},
watch: {
rate(v) {
this.$nextTick(() => {
this.pieInit();
});
},
},
mounted() {
this.pieInit();
},
methods: {
pieInit() {
const canvas = this.$refs.ratePie;
const ctx = canvas.getContext("2d");
// 垂直从270度开始, 后续转动角度 = (比例值 * 360) + 270
const deg = 270 + (parseFloat(this.rate) / 100) * 360;
// console.log('deg', deg);
const radius = 8//Number(this.radius);
const x = 8//ctx.canvas.height / 2;
const y = 8//ctx.canvas.width / 2;
const color = "#0740FF";
ctx.beginPath();
// ctx.translate(100, 100);
ctx.moveTo(x, y);
ctx.arc(x, y, radius, this.getRad(270), this.getRad(deg));
ctx.closePath();
// ctx.lineTo(0, 0);
// ctx.strokeStyle = color;
// ctx.stroke();
ctx.fillStyle = color;
ctx.fill();
},
// 1° = π / 180
// 1弧度 = 180° / π
// 获取弧度
getRad(deg) {
return (Math.PI * deg) / 180;
},
// 获取度数
getDeg(rad) {
return (rad * 180) / Math.PI;
},
// getXY(min) {
// const r = this.radius;
// const x = Math.cos((deg * Math.PI) / 180) * r; // 已知半径和角度,求 x 轴的长度
// const y = Math.sin((deg * Math.PI) / 180) * r; // 已知半径和角度,求 y 轴的长度
// return { x, y };
// },
},
};
</script>
在后面提测阶段,发现canvas最外层边线失真模糊问题,研究了一下发现其实canvas本质上只是提供了一块画布,然后根据当前画布大小来绘图,但在不同屏幕上存在逻辑像素与物理像素的差别,其实就涉及到了设备像素比devicePixelRatio的概念,所展示出的图形势必会有拉伸,导致模糊。具体原因可自行百度,这边加上结局方案,原理其实就是先放大canvas画布,保证图片的展示保真度,再用物理缩小。
const canvas = document.createElement('canvas');
// 获取到屏幕倒是是几倍屏。
const getPixelRatio = function(context) {
const backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
// iphone6下得到是2
const pixelRatio = getPixelRatio(canvas);
// 设置canvas的真实宽高
canvas.width = pixelRatio * canvas.offsetWidth; // 相当于 2 * 375 = 750
canvas.height = pixelRatio * canvas.offsetHeight;
canvas.style.width = pixelRatio * canvas.offsetWidth;
canvas.style.height = pixelRatio * canvas.offsetHeight;
// 此处绘图的时候也需要 * devicePixelRatio 和放大的 canvas 相匹配
ctx.arc(100 * pixelRatio, 100 * pixelRatio, 2 * pixelRatio, 0, 2 * Math.PI);
不过实际体验中,发现似乎并不能处理的很好,因为UI的设计稿上的比例图真的太小了(18px*18px),把我给整麻了,可能当时开发时间太紧张,所以也没用很充分的调试时间,后面就用svg重新设计了一下,简单处理了,同理主要还是从90°作为起点处理一下,此处记录一下,其实这组件应该还是能优化,原本都没打算用<circle>,但是没时间细调了= =
<template>
<svg class="ratePie" ref="ratePie" id="ratePie" xmlns='http://www.w3.org/2000/svg' version="1.1" :style="ratePieStyle">
<circle v-show="showType === 'circle'" ref="ratePieCircle" />
<path v-show="showType === 'pie'" ref="ratePiePath" />
</svg>
</template>
<script>
export default {
props: {
rate: {
type: [Number, String],
default: 10,
},
// 外层盒子大小
size:{
type: Number,
default: 16
}
},
computed: {
ratePieStyle() {
const style = {
width: `${this.size}px`,
height: `${this.size}px`,
}
return style;
}
},
watch: {
rate(v) {
this.$nextTick(() => {
this.pieInit();
});
},
},
data() {
return {
showType: 'pie'
}
},
mounted() {
this.pieInit();
},
methods: {
draw() {
const step = [];
const cxy = Number(this.size / 2); // 中心 x 和 y
const r = cxy;
const deg = Number(this.rate) / 100 * 360; // 转动角度
const rad = this.getRad(deg); // 弧度
const pointx = cxy + (cxy * Math.sin(rad)); // 结束点 x
const pointy = cxy - (cxy * Math.cos(rad)); // 结束点 y
step.push(`M ${cxy} ${cxy} L ${r} 0`);
step.push(' ');
step.push(`A ${r} ${r}, 0, ${deg > 180 ? 1 : 0}, 1, ${pointx} ${pointy}`);
step.push(' ');
step.push('Z');
return step.join('');
},
pieInit() {
const rate = Number(this.rate);
if(rate <= 0) {
this.showType = 'none';
} else if(rate >= 100) {
const circle = this.$refs.ratePieCircle;
if(circle) {
const cxy = Number(this.size / 2);
circle.setAttribute('cx', cxy);
circle.setAttribute('cy', cxy);
circle.setAttribute('r', cxy);
circle.setAttribute('stroke', 'none');
circle.setAttribute('fill', '#0740FF');
}
this.showType = 'circle';
} else {
const path = this.$refs.ratePiePath;
if(path) {
path.setAttribute('stroke', 'transparent');
path.setAttribute('stroke-width', 1);
path.setAttribute('fill', '#0740FF');
path.setAttribute('d', this.draw());
}
this.showType = 'pie';
}
},
},
};
</script>