[Из песочницы] Как я делал виджет управления мощностью для своего браузерного симулятора космических полетов

Сегодня я сделал небольшой сниппет кода для себя и решил поделиться с сообществом его содержимым и историей его создания.Я довольно долго рылся в интернетах для того чтобы найти хоть что-то похожее на то, что мне нужно, но интернет до сих пор с такой задачей не сталкивался видимо или никто не решился создавать подобный виджет. Наверное никому не надо было.

Для начала озвучу чего мне так хотелось: Мне нужен слайдер — аналог регулятора громкости, совмещенный с прогресс-баром. Эдакий компонент управления мощностью чего-либо, совмещенный с одновременной индикацией этой мощности. Иногда мощность может превышать установленный предел в 100% — необходимо отображать этот уровень и правильно высчитывать процент. Иногда мощность может заходить ниже нуля (не знаю может ли —, но я на всякий случай предусмотрел такую возможность) и этот уровень тоже надо отображать. Более того, то устройство, которое мы регулируем может быть инертным и разгоняться не с той скоростью, с которой мы выставляем значение. Если вы нажали кнопку форсажа на самолете — то двигатели выйдут на форсажный режим через некоторое время. То есть надо отдельно задавать значение прогрессбара и также отдельно получать-устанавливать текущее значение ползунка слайдера.

Может быть я и фиговый искатель, но в итоге психанул — решил сделать свой: Здесь ссылка на результат, а под катом описание процесса

НачинаемДля начала создадим каркас виджета: var PowerControlWidget = function (settings){ this.container = settings.container || undefined; this.canvas = document.createElement ('CANVAS'); this.canvas.height = this.height; this.canvas.width = this.width; this.container.appendChild (this.canvas); this.ctx = this.canvas.getContext ('2d');

// --- Набор полезных функций

this.set_value (0); this.redraw () }

Сразу оговорюсь — мне бы хотелось по максимуму быть независимым от jquery.js и jqueryui.js — поэтому я не стал оформлять этот виджет как плагин jQuery.

Обработка событий Для драг-н-дропов все банально: на mousedown сохраняем состояние, на mouseup — сбрасываем.

var self = this; this.canvas.addEventListener («mousedown», function (event){ self.mouse_down = true; self.value = event.offsetX; if (event.offsetX < self.padding_left_right){ self.value = self.padding_left_right; } if(event.offsetX > self._line_width — self.padding_left_right){ self.value = self._line_width — self.padding_left_right; } self._percent_value = self._get_percent (self.value); self.redraw (); self.onchange (self._percent_value, self.progress_value); }) this.canvas.addEventListener («mouseup», function (event){ self.mouse_down = false; self._percent_value = self._get_percent (self.value); self.redraw (); self.onchange (self._percent_value, self.progress_value); })

this.canvas.addEventListener («mousemove», function (event){ if (self.mouse_down){ self.value = event.offsetX;

if (event.offsetX < self.padding_left_right){ self.value = self.padding_left_right; } if(event.offsetX > self._line_width — self.padding_left_right){ self.value = self._line_width — self.padding_left_right; } self._percent_value = self._get_percent (self.value); self.redraw (); self.onslide (self._percent_value, self.progress_value); } }) Здесь довольно много вспомогательных переменных, которые задают позицию только в нужном месте этого канваса. В виджете, помимо его основной части надо также еще добавить и вывод точной информации — текущее значение прогрессбара и слайдера. Я и решил это делать внутри канваса, хоть тут мы и натыкаемся на вполне понятные проблемы с позиционированием текста. Вывод текста — чуть позже.

Начинаем рисование Для самого контрола слайдера мы выделяем некоторую область от этого канваса. Он у нас будет ограничен:

шириной this._line_width — Шириной полоски слайдера отступом слева-справа отступом сверху-снизу примерно вот так:

this._draw_border = function (){ var b = this.padding_top_bottom; // вспомогательный параметры для кривой безье. var a = this.padding_left_right; var w = this._line_width — (2 * a); var h = this.height — (2*b); this.ctx.beginPath (); this.ctx.moveTo (a, b); this.ctx.bezierCurveTo (a+(w/2), b, w-(w/2)+a, b, a+w, b); this.ctx.bezierCurveTo (a+w+a, b, a+a+w, b+h, a+w, b+h); this.ctx.bezierCurveTo (w/2+a, b+h, w/2+a, b+h, a, b+h); this.ctx.bezierCurveTo (0, b+h, 0, b, a, b); this.ctx.closePath (); this.ctx.strokeStyle = this.border_color; this.ctx.stroke (); // рисуем границу нужным цветом }; Напомню, что кривая безье содержит во входных параметрах три точки. Четвертая точка — текущая, мы должны в нее перейти с помощью moveTo.общий смысл рисования такой кривой: image

Получаем красивую рамочку с закруглёнными концами.

Теперь начинаем магию: Для того чтобы нарисовать зону отрицательного процентажа и зону форсажа мы будем пользоваться клипами. Клип — это просто, сначала создаем путь, внутри которого происходит рисование, а потом повторяем рисование границы с одно лишь разницей — мы будем не обводить этот путь, а заливать его изнутри нужным цветом. Звучит банально и просто, выглядит тоже не сложно.

Сначала определям, докуда его рисовать, все что до нуля — рисуем отдельным цветом.

var zero = this._get_x (0); // Магическое число ноль в исходном коде ставить можно

Создаем область отрисовки this.ctx.beginPath (); this.ctx.rect (0,0, zero, this.height); this.ctx.clip ();

И заливаем ту же самую безье нужным цветом.

this.ctx.beginPath (); this.ctx.bezierCurveTo (a+(w/2), b, w-(w/2)+a, b, a+w, b); this.ctx.bezierCurveTo (a+w+a, b, a+a+w, b+h, a+w, b+h); this.ctx.bezierCurveTo (w/2+a, b+h, w/2+a, b+h, a, b+h); this.ctx.bezierCurveTo (0, b+h, 0, b, a, b); this.ctx.fillStyle = this.below_z_color; this.ctx.fill (); this.ctx.closePath (); this.ctx.restore ();

По аналогии поступаем с областью выше 100%.

Немножко о вычислениях Нам нужно задать значение на виджете и забирать его, преобразуя координаты курсора мыши в процент и наоборот.Я для этого написал две очень простых функции:

this._get_percent = function (x){ var a = this.padding_left_right; // отступы слева и справа var w = this._line_width — (2*a); // ширина слайдера return ((x — a) * this._range/ w)+this.starting_percent; // Вычитаем из координат мыши ширину отступа, умножаем на разброс от стартового процента до конечного, делим на ширину слайдера и добавляем стартовый процент. }; this._get_x = function (p){ var a = this.padding_left_right; var w = this._line_width — (2*a); return a+ (p — this.starting_percent) * w / this._range; // Наоборот }; Ну и немного о рисовании текста Хотите одновременно сделать наглядным и точным? Пожалуйста, но тогда без текстовых данных не обойтись.Будем рисовать текст справа от линейки индикатора. Отдельно будем рисовать состояние слайдера, отдельно прогрессбара. Я думаю, можно поэкспериментировать с расположением, но пока сделаем так.

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

var val = this._percent_value * 100 var int = Math.floor (val); var frac = Math.floor ((val — int)*100);

То есть получили отдельно целую, отдельно дробную части. Рисовать их будем разного размера, но для начала вообще подсчитаем, какой нам лучше использовать шрифт. Я решил сделать простую зависимость, которая не слишком хорошо работает в некоторых условиях: var base_font_size = this.height — (this.padding_top_bottom*2) ; // размер шрифта целой части var add_font_size = Math.floor (base_font_size / 2); // размер шрифта дробной части var base_marg = base_font_size *2; // Отступ слева

Ну и в конце — код для отрисовки текста this.ctx.save () this.ctx.translate (this._line_width+ this.padding_top_bottom, this.height-this.padding_top_bottom); this.ctx.fillStyle = »#000»; this.ctx.font = base_font_size + «pt Arial»; this.ctx.textAlign = «end»; // Алигн по концу строки this.ctx.fillText (» + (int), base_marg, 0) this.ctx.textAlign = «center»; this.ctx.font = (base_font_size -2) + «pt Arial»; this.ctx.fillText (»,», base_marg+1,0) this.ctx.font = add_font_size + «pt Arial»; this.ctx.textAlign = «start»; // Алигн по началу строки this.ctx.fillText (» + (frac), base_marg+3, 0) this.ctx.restore ();

Заключение Готовый индикатор можно использовать. Настройки цветов для него могут задаваться напрямую. К сожалению решение использовать canvas не оставило для нас широких возможностей для расскрасски его с помощью css, Но у канваса другие преимущества — в частности с его помощью можно навешивать на этот индикатор дополнительные штрихи и линейки. Благо, что канвас может очень точно рисовать геометрические фигуры.

Для желающих поковырять его или воспользоваться оставляю адрес репозитория github.com/stavenko/power-control-widget. Сегодня этот виджет работал только с одним браузером — Google Chrome, и я если честно не уверен, что события будут правильно отрабатываться в других браузерах. В частности — в событиях может не быть координат мыши в переменных offsetX. А это было очень удобно — не надо вычислять координаты — они сразу даются относительно верха-лева контейнера.

На этом сегодня все.

© Habrahabr.ru