preface
Using ESM+TS style to write a similar effect of the old version of Alipay credit score (will move!!) ;
I started out writing it in the normal ES5+ style, and both versions of the code will be shown,
The version of the module has been added with a few additional details that interested viewers can take a look at
Effect drawing and Demo
Detailed renderings can be seen at Codesanbox
Codesanbox: codesandbox. IO/s / 4 rvo5mwxj…
See README for details
Making: github.com/crper/canva…
What can be gained?
The code has a bunch of comments.
My implementation ideas and coding posture, and some typescript usage
code
Version 1: nonESM
The style of
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
display: block;
margin: 0 auto;
background-image: linear-gradient(to top, #a3bded 0%, #6991c7 100%);
}
#test-action {
width: 200px;
height: 50px;
font-weight: 700;
background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
color: # 333;
font-size: 16px;
line-height: 50px;
border-radius: 50px;
text-align: center;
cursor: pointer;
margin: 50px auto;
}
</style>
</head>
<body>
<div id="test-action">Click me to see the random effect</div>
<script>
window.addEventListener('DOMContentLoaded'.function () {
/** @type {HTMLCanvasElement} */
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let ratio = window.devicePixelRatio; / / pixel
canvas.id = "credit-score";
canvas.width = 375;
canvas.height = 375;
canvas.style.width = "375px";
canvas.style.height = "375px";
document.body.appendChild(canvas);
// Initialize some values
// Canvas moves counterclockwise by default, from the start Angle to the end Angle
const initParams = {
w: canvas.width * ratio, // Width of canvas
h: canvas.height * ratio, // Height of canvas
x: canvas.width * ratio / 2.// Center coordinates x,y
y: canvas.width * ratio / 2.// Center coordinates x,y
startAngle: 165.// The beginning of the canvas
endAngle: 375.// The end of the canvas
currentAngle: 165.// The current Angle
scoreStart: 450./ / the start points
scoreTarget: 770./ / target points
scoreMin: 450./ / the lowest points
scoreMax: 850./ / the highest
scoreEvaDate: Evaluation Date: 2019-04-01.// Evaluation date
segAngle: 84.// The total Angle is divided into several equal parts
stepAngle() { // How many degrees each time
return (this.endAngle - this.startAngle) / this.segAngle;
},
outerTextSeg: 10.// The number ranges are equally divided
outerText() {
let textGap = Math.ceil(this.scoreRange() / (this.outerTextSeg - 1));
let textArr = Array.from(new Array(this.outerTextSeg), ((item, index) = > textGap * index + this.scoreMin));
let textStepAngel = this.angelRange() / (textArr.length - 1);
let textAngelArr = textArr.map((item, index) = > this.startAngle + textStepAngel * index);
return {
textArr,
textLenght: textArr.length,
textStepAngel,
textAngelArr
};
},
scoreRange() { // Range of scores
return this.scoreMax - this.scoreMin;
},
angelRange() { // Angle range
return this.endAngle - this.startAngle
},
scoreLevelText(text) {// Credit rating
let threeRangeScore = Math.ceil(this.scoreRange() / 3);
if (text) {
return text;
} else {
if (this.scoreStart <= this.scoreMin + threeRangeScore) {
return 'To be improved'
}
if (this.scoreStart > threeRangeScore && this.scoreStart <= this.scoreMin + threeRangeScore * 2) {
return 'Good credit'
}
if (this.scoreStart > this.scoreMin + threeRangeScore * 2 && this.scoreStart <= this.scoreMax) {
return 'Excellent credit'}}},style: {
line: { // Line color control
initColor: "Rgba (255, 191, 150, 0.5)".// Initialize the color
activeColor: "#fff".// Highlight the color
width: 1 // Line's promise
},
dashLine: {
initColor: "Rgba (255, 191, 150, 0.5)".// Initialize the color
activeColor: "#fff".// Highlight the color
width: 1 // The thickness of the line
},
text: { // Text color
outerText: { // Outer ring text
fontSize: 12.color: "#fff",},innerText: {
score: {
fontSize: 36.color: "#fff",},level: {
fontSize: 18.color: "#fff",},date: {
fontSize: 12.color: "#f2f2f2".fontWeight: "normal"
}
}
}
}
}
let pointImg = new Image();
pointImg.src = "data:image/svg+xml; base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd 2lkdGg9IjE2cHgiIGhlaWdodD0iMjRweCI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiAgZmlsbD0icmdiKDI1NSwgMjU1LCAyNTUpIiBkPSJNOC4wMDAsM jQuMDA1IEMzLjU4MiwyNC4wMDUgLTAuMDAwLDIwLjUyNSAtMC4wMDAsMTYuMjMwIEMtMC4wMDAsMTEuOTM2IDguMDAwLC0wLjAwNSA4LjAwMCwtMC4wMDUgQ zguMDAwLC0wLjAwNSAxNS45OTksMTEuOTM2IDE1Ljk5OSwxNi4yMzAgQzE1Ljk5OSwyMC41MjUgMTIuNDE4LDI0LjAwNSA4LjAwMCwyNC4wMDUgWk04LjAwM CwxMi4wNjUgQzUuNjI1LDEyLjA2NSAzLjcwMCwxMy45MzQgMy43MDAsMTYuMjM5IEMzLjcwMCwxOC41NDQgNS42MjUsMjAuNDEzIDguMDAwLDIwLjQxMyBDM TAuMzc1LDIwLjQxMyAxMi4zMDAsMTguNTQ0IDEyLjMwMCwxNi4yMzkgQzEyLjMwMCwxMy45MzQgMTAuMzc1LDEyLjA2NSA4LjAwMCwxMi4wNjUgWiIvPjwvc 3ZnPg=="
ctx.scale(1 / window.devicePixelRatio, 1 / window.devicePixelRatio);
// Get radians
function getRadian(degrees) {
return Math.PI / 180 * degrees;
}
// Get the degree
function getDegrees(radian) {
return 180 / Math.PI * radian
}
// Get the coordinates of the points on the edge of the circle
function getRadiusPoint(x, y, radius, degrees) {
return {
x1: x + radius * Math.cos(degrees),
y1: y + radius * Math.sin(degrees)
}
}
// Draw the peripheral text
function drawOuterText(x, y, text, fontSize = 28, color = "#fff", fontWeight = "normal") {
ctx.beginPath();
ctx.fillStyle = color;
ctx.font = `${fontWeight} ${fontSize * ratio}px Microsoft yahei`;
ctx.textBaseline = 'ideographic';
ctx.textAlign = "left";
ctx.fillText(text, x - ctx.measureText(text).width / 2, y);
}
// Draw the center text
function drawInnerText(x, y, text, fontSize = 30, color = "#fff", fontWeight = "bold") {
ctx.save();
ctx.fillStyle = color;
ctx.font = `${fontWeight} ${fontSize * ratio}px Microsoft yahei`;
ctx.textAlign = "center";
ctx.fillText(`${text}`, x, y);
ctx.textBaseline = 'ideographic';
ctx.restore();
}
// Draw little dots
function drawCircle(x, y, fillColor, mode = false) {
ctx.beginPath();
ctx.arc(x, y, initParams.style.line.width * ratio, 0.2 * Math.PI, mode);
ctx.fillStyle = fillColor;
ctx.fill();
}
/ / draw water droplets
function waterDrop(x = 60, y = 180, rotate = - 120.) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(getRadian(rotate));
ctx.drawImage(pointImg, 0.0.8 * ratio, 12 * ratio);
ctx.restore();
}
// Move the drop
function moveWaterDrop(x, y, radius, angle) {
const {x1, y1} = getRadiusPoint(x, y, radius, getRadian(angle));
// The reason why we add 90° is because the image is vertical by default, we need to correct it from the initial value of the coordinate system
waterDrop(x1, y1, angle + 90);
}
// Draw a dotted arc
function drawCircleDashLine({color}, {x, y, radius}) {
ctx.beginPath();
for (let i = 1; i <= initParams.segAngle; i++) {
const{x1, y1} = getRadiusPoint(x, y, radius, getRadian( initParams.startAngle + initParams.stepAngle() * i)); drawCircle(x1, y1, color); }}// Draw a solid arc
function drawCircleLine({color}, {x, y, radius}) {
ctx.beginPath()
ctx.arc(x, y, radius, getRadian(initParams.startAngle), getRadian(initParams.endAngle));
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.lineWidth = 1 * ratio;
ctx.stroke();
}
// Draw the outer text line
function drawOuterTextLine({outerText: {textArr, textAngelArr, textStepAngel, textLenght}, color}, {x, y, radius}) {
// The outermost text layer
for (let index = 0; index < textLenght; index++) {
let angle = getRadian(textAngelArr[index]);
const {x1, y1} = getRadiusPoint(x, y,radius, angle);
drawOuterText(x1, y1, textArr[index], initParams.style.text.outerText.fontSize, initParams.style.text.outerText.color)
}
}
// Clear the canvas content
function clearCanvas() {
ctx.clearRect(0.0, initParams.w, initParams.h);
}
// Draw the base map, that is, the initialization map
function drawBaseMap() {
const {score, level, date} = initParams.style.text.innerText;
clearCanvas();
drawOuterTextLine({
outerText: initParams.outerText(),
}, {
x: initParams.x,
y: initParams.y,
radius: initParams.x * 0.82
});
drawCircleLine(
{
color: initParams.style.line.initColor
},
{
x: initParams.x,
y: initParams.y,
radius: initParams.x * 0.7
});
drawCircleDashLine(
{
color: initParams.style.dashLine.initColor,
}
,
{
x: initParams.x,
y: initParams.y,
radius: initParams.x * 0.6666666});// Text position ()
drawInnerText(initParams.x, initParams.y - 10 * ratio, initParams.scoreStart, score.fontSize, score.color);
drawInnerText(initParams.x, initParams.y + 20 * ratio, initParams.scoreLevelText(), level.fontSize, level.color);
drawInnerText(initParams.x, initParams.y + 40 * ratio, initParams.scoreEvaDate, date.fontSize, date.color, date.fontWeight);
// Overwrite the solid line
drawCircleLine(
{
color: initParams.style.line.activeColor,
},
{
x: initParams.x,
y: initParams.y,
radius: initParams.x * 0.7
});
// Overwrite the dotted line
drawCircleDashLine(
{
color: initParams.style.dashLine.activeColor,
}
,
{
x: initParams.x,
y: initParams.y,
radius: initParams.x * 0.6666666});// Move the water drop
moveWaterDrop(initParams.x, initParams.y, initParams.x * 0.6, initParams.currentAngle);
// range of infinite render
if (initParams.scoreStart < initParams.scoreTarget) {
// The range of degrees of each move
initParams.currentAngle += initParams.stepAngle();
// Text changes
// Find the number of fractions required for each move
let stepScore = (initParams.scoreRange() * initParams.stepAngle()) / initParams.angelRange();
initParams.scoreStart = initParams.scoreStart + Math.round(stepScore);
if (initParams.scoreStart >= initParams.scoreTarget) {
initParams.scoreStart = initParams.scoreTarget
}
// Compare the current Angle with the fractional Angle. If the current accumulated Angle is less than the Angle needed to move to the destination, continue rendering
let stepAngle = initParams.startAngle + (initParams.angelRange() * ((initParams.scoreTarget - initParams.scoreMin)) / initParams.scoreRange());
if (initParams.currentAngle >= stepAngle) return false;
window.requestAnimationFrame(drawBaseMap); }}let st = setTimeout((a)= > {
clearTimeout(st);
drawBaseMap();
}, 1000);
function randomHexColor() { // Randomly generate hexadecimal colors
var hex = Math.floor(Math.random() * 16777216).toString(16); // Generate a hexadecimal number within FFFFFF
while (hex.length < 6) { // The while loop evaluates the hex number. If the number is less than 6, add 0 to make up 6 digits
hex = '0' + hex;
}
return The '#' + hex; // Return the hexadecimal color starting with '#'
}
// Reset random play
document.getElementById('test-action').addEventListener('click'.function () {
// Click reset
initParams.scoreStart = 450;
initParams.scoreTarget = Math.round(Math.random() * 400) + 450;
initParams.currentAngle = 165;
initParams.segAngle = [42.84.168.336] [Math.ceil(Math.random() * 3)];
initParams.outerTextSeg = [5.10.15.20] [Math.ceil(Math.random() * 3)];
let randomColor = randomHexColor();
initParams.style = {
line: { // Line color control
initColor: "Rgba (255, 191, 150, 0.5)".// Initialize the color
activeColor: randomColor, // Highlight the color
width: Math.random() * 1 + 1 // Line's promise
},
dashLine: {
initColor: "Rgba (255, 191, 150, 0.5)".// Initialize the color
activeColor: randomColor, // Highlight the color
width: Math.random() * 1 + 1 // Line's promise
},
text: { // Text color
outerText: { // Outer ring text
fontSize: 12.color: randomColor,
},
innerText: {
score: {
fontSize: 36.color: randomColor,
},
level: {
fontSize: 18.color: randomColor,
},
date: {
fontSize: 12.color: randomColor,
fontWeight: "normal"
}
}
}
}
drawBaseMap()
})
})
</script>
</body>
</html>
Copy the code
Version 2: Released NPM,ESM+TS
The style of
Code: github.com/crper/canva…
conclusion
The company had a need, and I had never used Canvas before, so I had to climb the pit by myself.
Generally speaking, the standard posture of canvas is not complicated. The complexity lies in mathematics.
Writing this high school math that I’ve been brushing up on, the release of the ESM module, packaged with rollup,
It’s a great tool. I’ll write typescript-rollup-startKit sometime
If there is something wrong, please leave a message and we will fix it in time. Thank you for reading.