I want to animate the speedometer using canvas. But it is necessary for me that the speedometer needle be triangular in shape and when the value changes, it shows the required value, but its base always remained in the center. Tell me what formula or algorithm to apply for this. The code is presented just below.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <canvas id="canvas" width="500" height="500"></canvas> <script> const canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), // general settings middleX = canvas.width / 2, middleY = canvas.height / 2, radius = 240, counterClockwise = false, // ticks settings tickWidth = canvas.width / 100, // tickColor = "#746845"; tickOffsetFromArc = canvas.width / 40, // Center circle settings centerCircleRadius = canvas.width / 20, centerCircleColor = "#ccc", centerCircleBorderWidth = canvas.width / 100, // Arrow settings arrowValueIndex = .73, arrowColor = "#464646", arrowWidth = canvas.width / 50, // numbers digits = [0, 20, 40, 50, 60, 70, 80, 90, 100], digitsColor = "#0a0a0a", digitsFont = "bold 20px Tahoma", digitsOffsetFromArc = canvas.width / 15, //zones zonesCount = digits.length - 1; // beginning and ending of our arc. Sets by radius*pi let startAngleIndex = .75, endAngleIndex = 2.25, step = (endAngleIndex - startAngleIndex) / zonesCount; /*draw zones*/ let DrawZones = function () { const greyZonesCount = zonesCount / 1.6; greenZonesCount = zonesCount - greyZonesCount, startAngle = (startAngleIndex - 0.02) * Math.PI, endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI, endGreenAngle = (endAngleIndex + 0.02) * Math.PI, //zones' options sectionOptions = [ { startAngle: startAngle, endAngle: endGreyAngle, color: "#e7e7e7", zoneLineWidth: 2 }, { startAngle: endGreyAngle, endAngle: endGreenAngle, color: "#13b74b", zoneLineWidth: 5 }, ]; this.DrawZone = function (options) { ctx.beginPath(); ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise); ctx.lineWidth = options.zoneLineWidth; ctx.strokeStyle = options.color; ctx.lineCap = "round"; ctx.stroke(); }; sectionOptions.forEach(options => this.DrawZone(options)); }; /*draw dots*/ let DrawTicks = function () { startAngleIndex = .73, endAngleIndex = 2.27, step = (endAngleIndex - startAngleIndex) / zonesCount; this.DrawTick = function (angle,count) { let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle), fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle), toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle), toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle), centerOfDotX=(fromX+toX)/2, centerOfDotY=(fromY+toY)/2; ctx.beginPath(); ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true); if (count<6){ switch (count) { case 1: case 2: case 3: ctx.fillStyle="#FF0000"; break; default: ctx.fillStyle="#F9AF00"; break; } }else{ ctx.fillStyle="#FFF"; ctx.strokeStyle="#13B74B"; ctx.shadowColor = "#a8bbaa"; ctx.shadowBlur = 15; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.stroke(); } ctx.fill(); ctx.closePath(); ctx.shadowBlur =0; }; let count=0; for (let i = startAngleIndex; i <= endAngleIndex; i += step) { let angle = i * Math.PI; count++; this.DrawTick(angle,count); } }; //draw numbers let DrawDigits = function () { let angleIndex = startAngleIndex; digits.forEach(function (digit) { let angle = angleIndex * Math.PI, x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle), y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle); angleIndex += step; ctx.font = digitsFont; ctx.fillStyle = digitsColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(digit, x, y); }); }; /*draw arrow РИСОВАНИЕ СТРЕЛКИ*/ let DrawArrow = function () { let arrowAngle = arrowValueIndex * Math.PI; let toX = middleX + (radius) * Math.cos(arrowAngle)+50; let toY = middleY + (radius) * Math.sin(arrowAngle)-50; ctx.beginPath(); ctx.moveTo(middleX, middleY); ctx.lineTo(toX, toY); ctx.strokeStyle = arrowColor; ctx.lineWidth = arrowWidth; ctx.stroke(); ctx.closePath(); }; window.onload=()=>{ DrawZones(); DrawTicks(); DrawDigits(); DrawArrow(); }; </script> </body> </html> 

    3 answers 3

    Here's another option, the old did not destroy, let there be a second answer:

     let canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), // general settings middleX = canvas.width / 2, middleY = canvas.height / 2, radius = 240, counterClockwise = false, // ticks settings tickWidth = canvas.width / 100, // tickColor = "#746845"; tickOffsetFromArc = canvas.width / 40, // Center circle settings centerCircleRadius = canvas.width / 20, centerCircleColor = "#ccc", centerCircleBorderWidth = canvas.width / 100, // Arrow settings arrowValueIndex = 0, arrowColor = "#464646", arrowWidth = canvas.width / 150, // numbers digits = [0, 20, 40, 50, 60, 70, 80, 90, 100], digitsColor = "#0a0a0a", digitsFont = "bold 20px Tahoma", digitsOffsetFromArc = canvas.width / 15, //zones zonesCount = digits.length - 1; // beginning and ending of our arc. Sets by radius*pi let startAngleIndex = .75, endAngleIndex = 2.25, step = (endAngleIndex - startAngleIndex) / zonesCount; /*draw zones*/ let DrawZones = function () { const greyZonesCount = zonesCount / 1.6; greenZonesCount = zonesCount - greyZonesCount, startAngle = (startAngleIndex - 0.02) * Math.PI, endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI, endGreenAngle = (endAngleIndex + 0.02) * Math.PI, //zones' options sectionOptions = [{ startAngle: startAngle, endAngle: endGreyAngle, color: "#e7e7e7", zoneLineWidth: 2 },{ startAngle: endGreyAngle, endAngle: endGreenAngle, color: "#13b74b", zoneLineWidth: 5 }]; this.DrawZone = function (options) { ctx.beginPath(); ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise); ctx.lineWidth = options.zoneLineWidth; ctx.strokeStyle = options.color; ctx.lineCap = "round"; ctx.stroke(); }; sectionOptions.forEach(options => this.DrawZone(options)); }; /*draw dots*/ let DrawTicks = function () { startAngleIndex = .73, endAngleIndex = 2.27, step = (endAngleIndex - startAngleIndex) / zonesCount; this.DrawTick = function (angle,count) { let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle), fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle), toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle), toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle), centerOfDotX=(fromX+toX)/2, centerOfDotY=(fromY+toY)/2; ctx.beginPath(); ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true); if (count<6){ switch (count) { case 1: case 2: case 3: ctx.fillStyle="#FF0000"; break; default: ctx.fillStyle="#F9AF00"; break; } } else { ctx.fillStyle="#FFF"; ctx.strokeStyle="#13B74B"; ctx.shadowColor = "#a8bbaa"; ctx.shadowBlur = 15; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.stroke(); } ctx.fill(); ctx.closePath(); ctx.shadowBlur =0; }; let count=0; for (let i = startAngleIndex; i <= endAngleIndex; i += step) { let angle = i * Math.PI; count++; this.DrawTick(angle,count); } }; //draw numbers let DrawDigits = function () { let angleIndex = startAngleIndex; digits.forEach(function (digit) { let angle = angleIndex * Math.PI, x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle), y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle); angleIndex += step; ctx.font = digitsFont; ctx.fillStyle = digitsColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(digit, x, y); }); }; /*draw arrow РИСОВАНИЕ СТРЕЛКИ*/ let DrawArrow = function () { ctx.beginPath(); ctx.moveTo(middleX-17, middleY-47); ctx.lineTo(middleX, middleY-180); ctx.lineTo(middleX+17, middleY-47); ctx.strokeStyle = arrowColor; ctx.lineWidth = arrowWidth; ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.arc(middleX, middleY, 50, Math.PI/8- Math.PI/2, 2 * Math.PI-Math.PI/8- Math.PI/2); ctx.stroke(); }; function draw() { ctx.clearRect(0,0,canvas.width,canvas.height); DrawZones(); DrawTicks(); DrawDigits(); ctx.translate(middleX,middleY); ctx.rotate(arrowValueIndex); ctx.translate(-middleX,-middleY); DrawArrow(); ctx.translate(middleX,middleY); ctx.rotate(-arrowValueIndex); ctx.translate(-middleX,-middleY); } window.onload = draw function val(value) { let sector = Math.PI*0.385 if (value < 40) arrowValueIndex = value/40*sector - sector*2; else arrowValueIndex = (value-40)/60*sector*3 - sector; document.querySelector('span').textContent = value; draw(); } 
     <input type="range" value="60" onmousemove="val(this.value)"><span></span><br> <canvas id="canvas" width="500" height="500"></canvas> 

    • Thank you so much :) - Tema4910
    • @ Tema4910 Yes, there are no questions, did you understand that no formulas were required? although it was possible to count it all - Stranger in the Q
    • How does the number in range compare with the numbers on the speedometer? - Grundy
    • @Grundy in any way, I wrote in another post that this is a problem, I need others to read - Stranger in the Q
    • @Grundy can be povpendrivatsya and zamapit correctly, but I did not, there first step 20 and then 10 - Stranger in the Q

    Sadly, the distribution of the scale is uneven, if it were uniform, one could do something

     function draw() { ctx.clearRect(0,0,canvas.width,canvas.height) //очистка канвы ctx.translate(middleX,middleY); // сдвиг в центр ctx.rotate(arrowValueIndex); // поворот табло ctx.translate(-middleX,-middleY); // сдвиг обратно DrawZones(); DrawTicks(); // рисует табло DrawDigits(); ctx.translate(middleX,middleY); // сдвиг в центр ctx.rotate(-arrowValueIndex); // обратный поворот табло ctx.translate(-middleX,-middleY); // сдвиг обратно DrawArrow(); // стрелка } 

     let canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), // general settings middleX = canvas.width / 2, middleY = canvas.height / 2, radius = 240, counterClockwise = false, // ticks settings tickWidth = canvas.width / 100, // tickColor = "#746845"; tickOffsetFromArc = canvas.width / 40, // Center circle settings centerCircleRadius = canvas.width / 20, centerCircleColor = "#ccc", centerCircleBorderWidth = canvas.width / 100, // Arrow settings arrowValueIndex = 0, arrowColor = "#464646", arrowWidth = canvas.width / 150, // numbers digits = [0, 20, 40, 50, 60, 70, 80, 90, 100], digitsColor = "#0a0a0a", digitsFont = "bold 20px Tahoma", digitsOffsetFromArc = canvas.width / 15, //zones zonesCount = digits.length - 1; // beginning and ending of our arc. Sets by radius*pi let startAngleIndex = .75, endAngleIndex = 2.25, step = (endAngleIndex - startAngleIndex) / zonesCount; /*draw zones*/ let DrawZones = function () { const greyZonesCount = zonesCount / 1.6; greenZonesCount = zonesCount - greyZonesCount, startAngle = (startAngleIndex - 0.02) * Math.PI, endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI, endGreenAngle = (endAngleIndex + 0.02) * Math.PI, //zones' options sectionOptions = [ { startAngle: startAngle, endAngle: endGreyAngle, color: "#e7e7e7", zoneLineWidth: 2 }, { startAngle: endGreyAngle, endAngle: endGreenAngle, color: "#13b74b", zoneLineWidth: 5 }, ]; this.DrawZone = function (options) { ctx.beginPath(); ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise); ctx.lineWidth = options.zoneLineWidth; ctx.strokeStyle = options.color; ctx.lineCap = "round"; ctx.stroke(); }; sectionOptions.forEach(options => this.DrawZone(options)); }; /*draw dots*/ let DrawTicks = function () { startAngleIndex = .73, endAngleIndex = 2.27, step = (endAngleIndex - startAngleIndex) / zonesCount; this.DrawTick = function (angle,count) { let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle), fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle), toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle), toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle), centerOfDotX=(fromX+toX)/2, centerOfDotY=(fromY+toY)/2; ctx.beginPath(); ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true); if (count<6){ switch (count) { case 1: case 2: case 3: ctx.fillStyle="#FF0000"; break; default: ctx.fillStyle="#F9AF00"; break; } }else{ ctx.fillStyle="#FFF"; ctx.strokeStyle="#13B74B"; ctx.shadowColor = "#a8bbaa"; ctx.shadowBlur = 15; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.stroke(); } ctx.fill(); ctx.closePath(); ctx.shadowBlur =0; }; let count=0; for (let i = startAngleIndex; i <= endAngleIndex; i += step) { let angle = i * Math.PI; count++; this.DrawTick(angle,count); } }; //draw numbers let DrawDigits = function () { let angleIndex = startAngleIndex; digits.forEach(function (digit) { let angle = angleIndex * Math.PI, x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle), y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle); angleIndex += step; ctx.font = digitsFont; ctx.fillStyle = digitsColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(digit, x, y); }); }; /*draw arrow РИСОВАНИЕ СТРЕЛКИ*/ let DrawArrow = function () { ctx.beginPath(); ctx.moveTo(middleX-17, middleY-47); ctx.lineTo(middleX, middleY-180); ctx.lineTo(middleX+17, middleY-47); ctx.strokeStyle = arrowColor; ctx.lineWidth = arrowWidth; ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.arc(middleX, middleY, 50, Math.PI/8- Math.PI/2, 2 * Math.PI-Math.PI/8- Math.PI/2); ctx.stroke(); }; function draw() { ctx.clearRect(0,0,canvas.width,canvas.height) ctx.translate(middleX,middleY); ctx.rotate(arrowValueIndex); ctx.translate(-middleX,-middleY); DrawZones(); DrawTicks(); DrawDigits(); ctx.translate(middleX,middleY); ctx.rotate(-arrowValueIndex); ctx.translate(-middleX,-middleY); DrawArrow(); } window.onload = draw; function val(value) { let sector = Math.PI*0.385 if (value < 40) arrowValueIndex = value/40*sector - sector*2; else arrowValueIndex = (value-40)/60*sector*3 - sector; document.querySelector('span').textContent = value; draw() } 
     <input type="range" value="60" onmousemove="val(this.value)"><span></span><br> <canvas id="canvas" width="500" height="500"></canvas> 

    • An interesting implementation :) Thanks for the example. But I want to implement the standard speedometer - Tema4910
    • @ Tema4910 so I did not understand what you want, do I have to turn the arrow? но ее положение всегда оставалось в центре - how then can this be understood? - Stranger in the Q
    • Yes, I explained rather strangely. I wanted to say that the base of the hand should remain in the center (as on the clock) when the value changes - Tema4910
    • @ Tema4910 I didn’t understand anyway, you can have some example, a link, a picture - Stranger in the Q
    • sindro.me/t/speedometer/speedometer.html PS only the arrow should be in the form of a triangle (As you can see in the example, the bottom of the arrow is always in the center when the value changes) - Tema4910

    I didn’t work with canvas, but look at the implementation data, perhaps they can help: http://www.knowstack.com/html5-canvas-speedometer/

    https://github.com/vjt/canvas-speedometer

    • Thanks, be sure to see) - Tema4910