Отрисовка стрелки спидометра в canvas

134
18 января 2020, 21:50

Хочу сделать анимацию спидометра с помощью canvas. Но мне необходимо, чтобы стрелка спидометра была треугольной формы и при изменении значения, она показывала на необходимое значение, но ее основание всегда оставалось в центре. Подскажите какую формулу или алгоритм необходимо применить для этого. Код представлен чуть ниже.

<!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>
Answer 1

Вот еще вариант, старый уничтожать не стал, пусть будет второй ответ:

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>

Answer 2

Печально что распределение шкалы неравномерное, если бы оно было равномерное можно было бы сделать нечто такое

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>

Answer 3

С canvas не работал, но посмотрите данные реализации, возможно помогут: http://www.knowstack.com/html5-canvas-speedometer/

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

READ ALSO
Помогите с ширеной меню на сайте

Помогите с ширеной меню на сайте

помогите пожалуйста с меню, нужно что бы на всех экранах оно было равно ширине 70% то есть на всю область сайтаКак можно это реализовать ?? давать...

124
Получить идентификаторы объекта

Получить идентификаторы объекта

Есть объект audio-postКак получить все идентификаторы <li> этого объекта? Как на JS отфильтровать полученные идентификаторы до чисел?

113
Не добавлять не уникальный обьект в массив

Не добавлять не уникальный обьект в массив

Есть массив обьектов fruits, из которого, обьекты могут добавляются в массив selected

148