В сети есть несколько похожих примеров создания спидометра, но я решил поделиться с вами своим.
Для начала нам нужно в DOM'е создать объект canvas:
Прописывать стили инлайн — это грех, но если ширину и высоту канвасу задать с помощью css, мы столкнёмся с массой проблем, а если быть точнее — это просто не будет работать. Теперь переходим непосредственно к скрипту. Сначала нам нужно получить 2d контекст, с которым мы дальше и будем работать:
Далее следует ряд настроек, которые нам понадобятся в вычислениях. Все значения размеров я высчитываю относительно ширины канваса. Таким образом, при изменении размеров канваса, пропорции спидометра не изменятся
Отдельно хотел бы остановиться на переменных startAngleIndex и endAngleIndex . Чтобы лучше понять, откуда взялись такие значения, я покажу картинку, взятую с сайта www.html5canvastutorials.com
Далее реализуем несколько методов, которые будут отрисовывать на канвасе элементы спидометра.
В этом методе я разбираю массив digits и определяю сколько должно быть зелёных, жёлтых и красных зон.
Метод DrawTicks рисует засечки на дуге с шагом, высчитанным ранее
Следующий метод отрисует нам цифры на будущем спидометре
Последние два метода предназначены для отрисовки центрального круга и, собственно, стрелки. При желании метод DrawArrow можно научить принимать нормальные значения и реализовать логику, которая будет определять, куда стрелка будет показывать
Осталось вызвать наши методы в нужном порядке
Конечно же, порядок вызова имеет значение, так же, как и, например, порядок слоёв в фотошопе (только в обратном порядке)
Результат:
Источники:
Для начала нам нужно в 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
Далее реализуем несколько методов, которые будут отрисовывать на канвасе элементы спидометра.
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 и определяю сколько должно быть зелёных, жёлтых и красных зон.
Метод 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);
}
};
Следующий метод отрисует нам цифры на будущем спидометре
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);
});
};
Последние два метода предназначены для отрисовки центрального круга и, собственно, стрелки. При желании метод 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();
Конечно же, порядок вызова имеет значение, так же, как и, например, порядок слоёв в фотошопе (только в обратном порядке)
Результат:
Источники:
Поделиться с друзьями
Комментарии (10)
Sirion
29.07.2016 10:21+4Рисование графическими примитивами, ня. Прямо школа вспомнилась. Восьмой класс, паскаль, «uses graph», машинки из кружочков и прямоугольничков…
Flakky
07.08.2016 19:19-1А скорость чего он, собственно, измеряет?)
Могу предложить несколькими простыми строками переписать его на измерение скорости анимации (через requestAnimationFrame() ) этого добра. Ваш спидометр тогда будет показывать FPS работы самого спидометра и будет пространство для оптимизации с мгновенным визуальным выводом результата :)
spmbt
Оптимизации нет. Если спидометром предполагается пользоваться для многократного изменения значения, 4 функции из 5, очевидно, вызываются избыточно, их стоит применить один первый раз. Далее, чтобы это выглядело не как Boilerplate code, надо обернуть в объект с методами init() и setValue(). А если учесть, что эксперименты (со снежинками на Canvas) показывают, что нарисовать один слой Canvas в браузерах затратнее, чем нарисовать или повернуть один DOM-элемент стрелки спидометра, то решение, очевидно, будет просто во вращении (css transform rotate, а лучше matrix) стрелки по setValue().
lekzd
Я бы еще вынес «фон» спидометра на отдельный canvas, который бы расположил под другим, в котором оставил бы только рисование стрелки
Alex_ME
Разве не наоборот? DOM затратнее, чем Canvas?
lekzd
CSS трансформации (не путать с изменнением layout) для DOM элементов работают несколько быстрее, потому для плавного перетаскивания и зума яндекс-карт используют CSS transform, а затем перерисовывают canvas