Доброго времени суток, дорогой читатель.
Что это такое?
в ходе моей разработки, я столкнулся с задачей создания функционала выбора временного промежутка. Первое что пришло в голову, это, конечно, самое простое и самое очевидное — несколько текстовых полей, куда вбивается начальное и конечное значения. Но разве это интересно? Нет.
Я решил немного соригинальничать.
Думаю, картинка довольно наглядно демонстрирует мою идею: имеем циферблат часов и 2 ползунка, которыми выбираем интервал. Хочу сразу сказать, что если я вдруг изобрел велосипед... хм, ну ладно, не смертельно. Все равно самому создать интереснее, чем пользоваться чьим то.
Скелет
Хочу сразу отметить, что я не являюсь web-дизайнером, и не обладаю навыками работы с CSS на уровне гуру, поэтому мой подход может кому то показаться топорным, не правильным.
Как вообще сделать кольцо на HTML?
- Создаем квадратную область со сторонами X.
- Задаем области значение border-radius со значением X. В итоге мы получаем круг.
- В центр круга помещаем аналогичную область, только с меньшим радиусом и другим цветом.
Кольцо готово, но нам нужно не целое кольцо, а только его половина, поэтому добавим еще один пункт
- Отрезаем часть области свойством clip таким образом, чтобы получился полукруг
Для конечного результата нам необходимо два «цветных» полукруга (кодовое название — створка ), и два полукруга с цветом фона (кодовое название — антистворка ).
Меняя положения ползунка, мы будем вращать створки. Антистворки нужны для того, чтобы скрыть створки, когда интервал выделения меньше 180 гр. Скрыть — это значит выводить антистворки поверх створок, меняя значение z-index.
Конечный «скелет» HTML должен выглядеть вот так:
Как это должно работать?
И так. Мы имеем два ползунка (кодовые имя — ползунок1 и ползунок2). При клике по ползунку, а точнее при событии onmousedown, он активируется для перемещения. Двигая курсор, меняем положение ползунка, затем отпускаем курсор (событие onmouseup), чтоб его даективировать.
Надо отметить, что движение курсора должно быть не просто хаотичным во все возможные стороны, словно мы формируем SSH-ключ через puttygen, необходимо чтоб движение, более или менее, напоминали круговые. Для этого при активации ползунка определяется зона, в которой перемещение возможно. При выходе за зону, ползунок деактивируется. Зона — две окружности с радиусами больше и меньше радиуса окружности, по которой перемещается ползунок.
С точки зрения формул все просто: мы считаем расстояние, на которое удален курсор от центра окружности. Проверяем попало ли это значение в интервал или нет.
onmousemove = function(e){
/*
все расчеты ведутся относительно верхнего левого угла родительного элемента.
А положение курсора считается от верхнего левого угла окна,
поэтому надо отнять от положения курсора положение родительского элемента
*/
var rect = Area.getBoundingClientRect();
var Xpoint = (e.clientX - area_radius) - rect.left;
var Ypoint = (e.clientY - area_radius) - rect.top;
var R = Math.sqrt(Math.pow(Xpoint,2)+Math.pow(Ypoint,2));
var a = (180*Math.acos((Xpoint-0)/R))/Math.PI;
a = (Ypoint>=0) ? a=360-a : a;//адекватное изменение угла, когда курсор ниже центра окружности
move(a,R);
};
Разные системы координат
Если все соответствует, то вычисляем угол отрезка между центром окружности и положением курсора. И тут всплывает один неудобный момент — разные системы координат( СК ): обычная, с начальным значением в крайней правой точки, увеличением угла при движении против часовой стрелки. И часовая. 00:00 у нас находится в крайней верхнем положении, и движение, соответственно, по часовой стрелке.
Элементы необходимо двигать в обычной СК, а часы вычислять в часовой.
Границы дозволенного
Каждый ползунок имеет границы: левую и праву, за которые ему нельзя перемещаться. Вот как раз создание этих границ и было самой большой загвоздкой.
Я потратил 3 дня на то чтобы придумать правильный… нет, это не верное слово… рабочий механизм обработки границ. Каждый, кто захочет повторить мой подвиг, придумает свой «правильный» механизм, с большим или меньшим успехом чем у меня. Я его полностью «перепридумывал» раза 3, когда доходя до какого то момента, осознавал, что при текущем механизме, не получится сделать какой то момент.
В итоге механизм выглядит так: каждый ползунок имеет 4 состояния (в коде, переменные, отвечающие за состояния, обозначены как sONE и sTWO). Вокруг них все и строится.
- -2 — крайнее правое положение ползунка. Значение часов 00:00.
- -1 — ползунок находится на первом обороте циферблата. Значение часов от 00:00 до 11:59.
- 1 — ползунок находится на втором обороте циферблата. Значение часов от 12:00 до 23:00.
- 2- крайнее левое положение ползунка. Значение часов 24:00
По факту при положениях 2 и -2, ползунки находятся в одной и той же точки, но разница в том, с какой стороны они туда пришли. Соответственно дальше они могут двигаться только обратно.
Что бы определить в какую сторону двигается ползунок, сверяется новое значение положения ползунка относительно старого. Например, чтобы определить момент перехода ползунка из первого оборота по циферблату во второй, нужно соблюсти условие, при котором предыдущее значение угла было в интервале: 60 < X, 0 < X , а новое 0==X; 330 < X. Все аналогичные проверки осуществляется примерно так же, т.е. не просто проверкой A > Б, а именно указанием интервала где можно находится А, а где Б. Просто можно дернуть ползунок резко и его значение сразу изменится на 10 или 15 градусов.
if(R>=low_limit_polzunok_ONE && R<=high_limit_polzunok_ONE){
angle_polzunok_ONE_last = angle_polzunok_ONE;
angle_polzunok_ONE = toClk(a);
if(barier_ONE==true){
if(angle_polzunok_ONE>300){
if(toClk(a)>angle_polzunok_TWO+1 && toClk(a)<angle_polzunok_TWO+60+1){
move_polzunok_ONE(toCoo(angle_polzunok_ONE));
barier_ONE=false;
}
}else{
if(toClk(a)>angle_polzunok_TWO+1){
move_polzunok_ONE(toCoo(angle_polzunok_ONE));
barier_ONE=false;
}
}
}else if(sONE==-2){//0 часов. ползунок в крайнем положении разрешено двигаться только ПО часовой
if(angle_polzunok_ONE>300){
sONE=-1;
move_polzunok_ONE(a);
}else{
angle_polzunok_ONE=360;
}
}else if(sONE==-1){
if(angle_polzunok_ONE>300 && (angle_polzunok_ONE_last>=0 && angle_polzunok_ONE_last<60)){
sONE=1;
move_polzunok_ONE(a);
}else if((angle_polzunok_ONE>=0 && angle_polzunok_ONE<60) && angle_polzunok_ONE_last>300){
sONE=-2;
angle_polzunok_ONE = 0;
move_polzunok_ONE(90);
}else{
move_polzunok_ONE(a);
}
}else if(sONE==0){//начальное значение 12 часов дня
if(angle_polzunok_ONE>300){
sONE=1;
move_polzunok_ONE(a);
}else{
sONE=-1;
move_polzunok_ONE(a);
}
}else if(sONE==1){
if((angle_polzunok_ONE>=0 && angle_polzunok_ONE<60) && angle_polzunok_ONE_last>300){
sONE=-1;
move_polzunok_ONE(a);
}else if((angle_polzunok_ONE==0 || angle_polzunok_ONE>300) && (angle_polzunok_ONE_last>0 && angle_polzunok_ONE_last<90)){
sONE=2;
angle_polzunok_ONE = 0;
move_polzunok_ONE(90);
}else{
move_polzunok_ONE(a);
}
}else if(sONE==2){//24 часа. ползунок в крайнем положении разрешено двигаться только ПРОТИВ часовой
if(angle_polzunok_ONE>0 && angle_polzunok_ONE<60){
sONE=1;
move_polzunok_ONE(a);
}else{
angle_polzunok_ONE=0;
}
}
//проверяем уперся ли наi ползунок в другой ползунок
if(sTWO==sONE && angle_polzunok_TWO+1>angle_polzunok_ONE){
barier_ONE = true;
angle_polzunok_ONE = angle_polzunok_TWO+1;
move_polzunok_ONE(toCoo(angle_polzunok_ONE));
}
}else{
polzunok_switch_ONE(false);
}
Определение времени
Собственно на предыдущем пункте закончилось все самое сложное и интересное. Процесс вычисления времени ничем особым не примечателен. Нехитрый расчет 360 гр = 12 часов * 60 минут показывает, что одному градусу соответствует две минуты. Зная градус ползунка в часовой системе координат, не составляет труда вычислить время. Если ползунок находится на втором обороте, нужно к значению градуса прибавить еще 360.
Весь листинг, а так же результат, можно посмотреть вот тут
На посошок
Моя цель была в создании подобной штуки не для JavaScript'a, а для GWT. Изначально легче разработать на JS, а потом перенести на GWT. Поэтому я себя и не утруждал созданием удобного конструктора, да и сама процедура расчета даты в качестве результат возвращает обычную текстовую строку.
Это черновик. Испытательный образец, но быть может он кому то будет полезен.
Спасибо за внимание, дорогой читатель.
PS Закончив писать пост, я осознал что можно было бы сделать все проще. Вот так обычно — «хорошая мысля приходит опосля».
Автор: kos1nus