Pull to refresh

Спидометр на HTML5 Canvas

Reading time 4 min
Views 20K
В сети есть несколько похожих примеров создания спидометра, но я решил поделиться с вами своим.

image

Для начала нам нужно в DOM'е создать объект canvas:

<canvas id="canvas" width="500" height="500"></canvas>

Прописывать стили инлайн — это грех, но если ширину и высоту канвасу задать с помощью css, мы столкнёмся с массой проблем, а если быть точнее — это просто не будет работать. Теперь переходим непосредственно к скрипту. Сначала нам нужно получить 2d контекст, с которым мы дальше и будем работать:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

Далее следует ряд настроек, которые нам понадобятся в вычислениях. Все значения размеров я высчитываю относительно ширины канваса. Таким образом, при изменении размеров канваса, пропорции спидометра не изменятся

// general settings
var middleX = canvas.width / 2;
var middleY = canvas.height / 2;
var radius = canvas.width / 2 - canvas.width / 10;
// beginning and ending of our arc. Sets by rad * pi
var startAngleIndex = 0.7;
var endAngleIndex = 2.3;

// zones settings
var zoneLineWidth = canvas.width / 30;
var counterClockwise = false;

// ticks settings
var tickWidth = canvas.width / 100;
var tickColor = "#746845";
var tickOffsetFromArc = canvas.width / 40;

// Center circle settings
var centerCircleRadius = canvas.width / 20;
var centerCircleColor = "#efe5cf";
var centerCircleBorderWidth = canvas.width / 100;

// Arrow settings
var arrowValueIndex = 1.29;
var arrowColor = "#464646";
var arrowWidth = canvas.width / 50;

// Digits settings
var digits = [0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240];
var digitsColor = "#746845";
var digitsFont = "bold 20px Tahoma";
var digitsOffsetFromArc = canvas.width / 12;

var zonesCount = digits.length - 1;
var step = (endAngleIndex - startAngleIndex) / zonesCount;

Отдельно хотел бы остановиться на переменных startAngleIndex и endAngleIndex . Чтобы лучше понять, откуда взялись такие значения, я покажу картинку, взятую с сайта www.html5canvastutorials.com

image

Далее реализуем несколько методов, которые будут отрисовывать на канвасе элементы спидометра.

var DrawZones = function() {
        var greenZonesCount = Math.ceil(zonesCount / 2);
        var yellowZonesCount = Math.ceil((zonesCount - greenZonesCount) / 2);
        var redZonesCount = zonesCount - greenZonesCount - yellowZonesCount;

        var startAngle = (startAngleIndex - 0.02) * Math.PI;
        var endGreenAngle = (startAngleIndex + greenZonesCount * step) * Math.PI;
        var endYellowAngle = (startAngleIndex + (greenZonesCount + yellowZonesCount) * step) * Math.PI;
        var endRedAngle = (endAngleIndex + 0.02) * Math.PI;

        var sectionOptions = [
            {
                startAngle: startAngle,
                endAngle: endGreenAngle,
                color: "#090"
            },
            {
                startAngle: endGreenAngle,
                endAngle: endYellowAngle,
                color: "#cc0"
            },
            {
                startAngle: endYellowAngle,
                endAngle: endRedAngle,
                color: "#900"
            }
        ];

        this.DrawZone = function(options) {
            ctx.beginPath();
            ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise);
            ctx.lineWidth = zoneLineWidth;
            ctx.strokeStyle = options.color;
            ctx.lineCap = "butt";
            ctx.stroke();
        };

        sectionOptions.forEach(function(options) {
            DrawZone(options);
        });
    };

В этом методе я разбираю массив digits и определяю сколько должно быть зелёных, жёлтых и красных зон.

image
Метод DrawTicks рисует засечки на дуге с шагом, высчитанным ранее

var DrawTicks = function() {

        this.DrawTick = function(angle) {
            var fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle);
            var fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle);
            var toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle);
            var toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle);

            ctx.beginPath();
            ctx.moveTo(fromX, fromY);
            ctx.lineTo(toX, toY);
            ctx.lineWidth = tickWidth;
            ctx.lineCap = "round";
            ctx.strokeStyle = tickColor;
            ctx.stroke();
        };

        for (var i = startAngleIndex; i <= endAngleIndex; i += step) {
            var angle = i * Math.PI;
            this.DrawTick(angle);
        }
    };

image
Следующий метод отрисует нам цифры на будущем спидометре

var DrawDigits = function() {
        var angleIndex = startAngleIndex;

        digits.forEach(function(digit) {
            var angle = angleIndex * Math.PI;
            angleIndex += step;
            var x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle);
            var y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle);

            ctx.font = digitsFont;
            ctx.fillStyle = digitsColor;
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText(digit, x, y);
        });
    };

image
Последние два метода предназначены для отрисовки центрального круга и, собственно, стрелки. При желании метод DrawArrow можно научить принимать нормальные значения и реализовать логику, которая будет определять, куда стрелка будет показывать

var DrawArrow = function() {
        var arrowAngle = arrowValueIndex * Math.PI;
        var toX = middleX + (radius) * Math.cos(arrowAngle);
        var toY = middleY + (radius) * Math.sin(arrowAngle);

        ctx.beginPath();
        ctx.moveTo(middleX, middleY);
        ctx.lineTo(toX, toY);
        ctx.strokeStyle = arrowColor;
        ctx.lineWidth = arrowWidth;
        ctx.stroke();
    };

    var DrawCenterCircle = function() {
        ctx.beginPath();
        ctx.arc(middleX, middleY, centerCircleRadius, 0, 2 * Math.PI, false);
        ctx.fillStyle = centerCircleColor;
        ctx.fill();
        ctx.lineWidth = centerCircleBorderWidth;
        ctx.strokeStyle = arrowColor;
        ctx.stroke();
    };

Осталось вызвать наши методы в нужном порядке

DrawTicks();
DrawZones();
DrawDigits();
DrawArrow();
DrawCenterCircle();

Конечно же, порядок вызова имеет значение, так же, как и, например, порядок слоёв в фотошопе (только в обратном порядке)
Результат:
image

Источники:

Tags:
Hubs:
+9
Comments 10
Comments Comments 10

Articles