В этой статье я расскажу как просто и непринужденно можно создавать линейные динамические меню поддерживающие не только события мыши, но и поинтеры для мобильных версий.
И так: Simple Dynamic Menu by RevolveR Labs.
Начинается все с верстки. Она должна быть семантической, легкой и современной.
<nav class="dynamic-menu">
<ul>
<li><a href="https://revolvercmf.ru">RevolveR Labs</a></li>
<li><a href="#">Ultra newest solutions</a></li>
<li><a href="#">The way of incredible</a></li>
<li><a href="#">In search of the best</a></li>
<li><a href="#">Progressive RevolveR frontends</a></li>
<li><a href="#">Developing of new era</a></li>
</ul>
</nav>
Мы используем стандартный маркированный список и HTML 5 в качестве элемента враппера, а чтобы сделать меню плавающим сразу пропишем CSS стили вытягивающие меню на за пределы экрана на всю ширину списка элементов и скроем все лишнее до области видимости:
.dynamic-menu {
display: inline-block;
text-align: center;
overflow: hidden;
margin: 0 auto;
height: 3vw;
width: 80%;
}
.dynamic-menu ul {
transition: all 2.5s ease-in-out;
position: relative;
list-style: none;
width: 900vw;
padding: 0;
margin: 0;
left: 0vw;
}
.dynamic-menu ul li {
box-shadow: 0 0 0.1vw #333;
border: .1vw dashed #fff;
background: #a2a2a2;
margin-bottom: 1vw;
display: inline-block;
border-radius: .2vw;
margin-right: .5vw;
padding: .2vw 1vw;
background: #888;
float: left;
}
.dynamic-menu ul li a {
text-shadow: 0 0 0.2vw #fff;
font: normal 2vw Helvetica;
text-decoration: none;
color: #006400;
}
.dynamic-menu ul li a:hover {
text-decoration: underline;
color: #674c2be0;
}
Наш CSS готов. Теперь меню будет расположено по центру и все элементы списка не отобразяться. Нам осталось написать хэндлеры меню, которые будут отвечать за плавное перемещение списка при нажатой левой кнопке мыши или событии touch.
Handler для desktop версии
Для работы хэндлера нам понадобится инициализировать RevolveR инстанс и использовать некоторое встроенное API работы с событиями:
let launch = RR.browser;
RR.menuMove = null;
if( !RR.isM ) {
RR.event('.dynamic-menu ul', 'mousedown', (e) => {
e.preventDefault();
if( !RR.menuMove ) {
RR.menuLeft = RR.curxy[0];
RR.MenuMoveObserver = RR.event('body', 'mousemove', (e) => {
e.preventDefault();
RR.styleApply('.dynamic-menu ul', ['transition: all 0s ease']);
RR.menuMove = true;
RR.menuPosition = ( RR.menuLeft - RR.curxy[0] ) *-1;
RR.styleApply('.dynamic-menu ul', ['left:'+ RR.menuPosition +'px']);
RR.event('body', 'mouseup', (e) => {
e.preventDefault();
if( e.target.tagName === 'A' && !RR.touchFreeze ) {
//R.loadURI(target.href, target.title);
console.log(e.target.href);
RR.touchFreeze = true;
RR.menuMove = null;
}
void setTimeout(() => {
RR.menuMove = null;
}, 50);
void setTimeout(() => {
if( !RR.menuMove ) {
RR.styleApply('.dynamic-menu ul', ['left: 0px', 'transition: all 2.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)']);
}
}, 2500);
});
});
}
});
}
Большинство необходимых event уже работают после запуска гетера RR.browser(). Это например отслеживание событий изменения размера окна и постоянно обновление положения указателя мыши RR.curxy.
Здесь мы используем метод блокировки флагами для того, чтобы добиться синхронности выполнения событий и задать блокирующие флаги, например, для реализации запрета события перехода по ссылке, если меню находится в движении.
RR.MenuMoveObserver является собой event стеком, который хранит MD5 hash события для того, чтобы можно было выключить часть хэндлера отвечающего за смену положения по оси X. Мы выключаем обсерверы каждый раз когда событие клик завершилось в пользу mouseup.
Готово. При нажатии на левую клавишу мыши, если держать кнопку утопленной будет происходить отслеживание положения курсора мыши по оси X, а обсервер обеспечит своевременное обновление положения left контейнера списка меню внутри враппера области видимости и лента меню начнет двигаться открывая не поместившиеся элементы списка.
Мобильный handler меню
Основной принцип работы остается примерно таким же, но я не стал схлопывать разные хэндлерры в один обработчик чтобы оставить понятность и читабельность кода.
Проверив, что инициализирован мобильный инстанс браузера мы подключим аналоги слушателей событий уже не для мыши, а для touch экрана.
if( RR.isM ) {
RR.event('.dynamic-menu ul', 'touchstart', (e) => {
e.preventDefault();
RR.menuMove = null;
RR.event('body', 'touchend', (e) => {
e.preventDefault();
if( !RR.menuMove ) {
RR.touchFreeze = null;
let target = e.changedTouches[0].target;
if( RR.isO(RR.MenuMoveObserver) ) {
for( i of RR.MenuMoveObserver ) {
RR.detachEvent( i[ 2 ] );
}
}
if( target.tagName === 'A' && !RR.touchFreeze ) {
//R.loadURI(target.href, target.title);
console.log(e.target.href);
RR.touchFreeze = true;
RR.menuMove = null;
}
void setTimeout(() => {
if( !RR.menuMove ) {
RR.styleApply('.dynamic-menu ul', ['left: 0px', 'transition: all 2.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)']);
//RR.animate('.dynamic-menu ul', ['left:0px:1000:wobble']);
}
}, 2500);
}
});
if( !RR.menuMove ) {
RR.menuLeft = e.changedTouches[0].screenX;
RR.MenuMoveObserver = RR.event('body', 'touchmove', (e) => {
e.preventDefault();
RR.styleApply('.dynamic-menu ul', ['transition: all 0s ease']);
RR.menuMove = true;
RR.menuPosition = ( RR.menuLeft - e.changedTouches[0].screenX ) *-1;
RR.styleApply('.dynamic-menu ul', ['left:'+ RR.menuPosition +'px']);
RR.event('body', 'touchend', (e) => {
RR.menuMove = null;
});
});
}
});
}
В коде вы увидите небольшую разницу. Во первых event.target теперь не работает и нужно следить за сериями touch. Я добавил анимацию возвращения меню с эффектом easing и теперь меню само плавно возвращается в начальное положение спустя некоторое время бездействия с меню:
void setTimeout(() => {
if( !RR.menuMove ) {
RR.styleApply('.dynamic-menu ul', ['left: 0px', 'transition: all 2.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)']);
}
}, 2500);
Demo
Чтобы посмотреть как работает Dynamic Menu на базе библиотеки RevolveR вы можете пройти по ссылке.
Итог
Многие из вас используют готовые плагины или встраивают блоки верстки с меню гамбургера, однако динамические линейные меню ни чуть не хуже и позволяют экономить место в интерфейсах. Многим из вас наверняка показалось бы сложным реализовать такие меню из-за разницы в слушателях событий и неумения использовать флаги. Этот пример был направлен показать, что средствами обычного JavaScript можно не обладая какой-то запредельной квалификацией реализовывать такие же вещи, как и в удобных Android приложениях, а мастерская работа с анимациями позволяет создавать потрясные эффекты переходов и разные хуки событий на какие-либо срабатывания.
devlev
Да тут просто ничего не работает, от слова совсем!
Full-R Автор
Что именно не работает. Подробнее.
devlev
Ну на словах сложно объяснить. Тут видео нужно записывать экрана. Под рукой такого софта нет.
Попробую объяснить на словах: сдвигаю меню влево примерно на 100-200px, далее бросаю курсор и снова двигаю меню на столько же, когда я третий раз пытаюсь передвинуть меню, после бросания курсора оно сразу уезжает на начальную позицию. Предполагается что меню не должно езжать пока курсор висит над меню. А если пунктов будет не 5 а 50?
Но я по стилю кода вижу, что баги будут 100%. Например, если вы создаете setTimeout но не как не используете clearTimeout. Если вы рассчитываете, что проверка if( !RR.menuMove ) спасет от повторного вызова, то вы глубоко ошибаетесь!
Хороший тон для каждого setTimeout писать где то рядышком clearTimeout чтобы если вдруг нужно отменить действие, это можно было сделать легко быстро!
Full-R Автор
Спасибо. Как то не учел лихорадочных и очень длинные списки. Будет время — добавлю ещё один флаг блокировки возврата. Если void стоит таймер типа setTimeout уничтожается после первого и единственного тика. Если void добавить к setIntetval, то таймер потеряет id и его невозможно будет остановить. Флагом просто проще сделать, если блокировку отката на hover повешать.
devlev
Вы ничего не поняли! Вот такой код:
void setTimeout(() => {
это плохо! Нужно писать:
const timer = setTimeout(() => {
и где-то должен быть возможен вызов
clearTimeout(timer)
Иначе у вас будет бесконечное число сайд эффектов, и вы только и дальше будите их плодить.
Full-R Автор
А вы хорошо уверены, что хороший тон константой nullable делать? Оно ведь не сбросится скорее всего.
devlev
Слово const используется чтобы самому себе не выстрелить в ногу: не перезаписать переменную и не забыть что надо удалить таймер в случае отмены эффекта.
Full-R Автор
В циклах for тоже можно const на итератор поставить и оно даже работает пока strict не включить. А я вот так никогда не сделаю.
Full-R Автор
Вот чуть доработанный код. Теперь у рыпнутых нет возврата меню когда они рыпают мышью, пальцами и в общем-то можно применять к длинным спискам. Если подскажете что можно улучшить — обновлю в публикацию.
devlev
Это называется спагетти код! У меня было пару программистов, которые писали подобные вещи. Они не хотели учиться. Уволены. Мой вам совет, выкиньте на помойку свои знания по Javascript и начните учиться современным методам программирования. То что вы пишите, писали в начале 2000 годов. Сейчас уже есть куча всего готового и удобного. А перед тем как изобретать свой велосипед нужно сначала изучить все остальные велосипеды: Typescript, React, Vue, Angular — список можно продолжать бесконечно.
Full-R Автор
А че вы Laravel сразу не пиарите? Ненавижу шестерок инфраструктуры, в которой они даже не разработчики. Захотелось разбить вам лицо.
owwyye
Милейший, вам таблеточки пить надо. Или в Красноуфимске все айтишники друг другу лицо бьют во время дискуссий?
Full-R Автор
Таблеточки пить надо после того как лицо разобьют. Для вас не милейший.
owwyye
Знаешь, я из научного интереса посмотрел твой вк. Ты настолько цельный персонаж что прямо хоть в палату мер и весов. Скажи, ты правда считаешь что в этих очках неотразим, мачо ты красноуфимский?
И про хабраэффект смешно ты написал.
Full-R Автор
Давайте от темы не отвлекаться. Я с этого и начинал. Отвечайте мне вот там же где это и было, если хотите. Нафиг ты мне срешь?
p.s.: можно ли зачистить offtop или как-то в спойлер свернуть? надоели.
vmkazakoff
Это такая реклама вашей штуки под названием РевольвеР? Вы сами же пробовали открыть свои демо? И сайт cmf дальше? С телефона тоже?
На всякий подскажу: полезно бывает дать ссылку человеку который не знает ничего ещё и посмотреть на его реакцию. Вот просто взять и в глаза заглянуть.
В данном случае там будет немой вопрос "за что?"… Не все разделяют вашу любовь к странным штриховке на прелоадере, думаю. А когда 3 прелоадера вместе вдруг появляются, это блин вообще жесть что творится.
По существу: вы сделали меню, которое можно было сделать одним css (overflow-x: scroll и если надо то можно ещё скроллбар убрать потом). А ещё ваше творение выглядит как поделка школьника, уж простите (
Full-R Автор
Какой у вас телефон? Я смартфон использую с Edge. У меня все работает. Недавно перестал обращать внимание на косяки FireFox из-за частых багов самого браузера и ориентируюсь в основном на Chromium браузеры. Preloader к статье не относится. Он просто версткой сделан и CSS и любой желающий может его убрать или переделать.
vmkazakoff
Меню получилось очень плохим. Совсем. Совершенно не пригодно ни для чего. Идеальный антипаттерн по всем параметрам.
— я не могу листать его боковым скроллом на тачпаде ноутбука
— листать горизонтально меню на десктопе зажав кнопку это жесть с точки зрения интерфейса и удобства
— поведение на мобиле это не свайп (который совсем другую физику имеет), а именно таскание, что ну абсолютно не удобно — я не могу быстро провести пальцем и прокрутить
— на десктопе легче сделать стрелки или любой другой аналог слайдера выбрать (да даже просто нативный скролл лучше в 100500 раз)
— вы наоверинжинирили длиннющий JS когда вообще можно было обойтись несколькими строками CSS (и то если думать про IE где надо скроллбар спрятать, а на хромиум так вообще одним стилем)
— при этом оверинжиниринге вы не предусмотрели 100500 в 100500 степени условий, за который юзеры будут вас не любить (попробуйте нажать правой кнопкой в любом месте вашего меню, а потом в любое другое место — не могу проверить с мобилы, но я уверен что вы дальше будете водить мышкой во все стороны и меню будет крутиться, хотя кнопку вы уже не жмете)
— да только за код написанный через строчку я бы джуна как минимум попросил так больше не делать, но вообще настройки вашего линтера я реально не хочу знать — мусье явно знает толк в извращениях
— страница с демо — блок меню с фоном #888, с темно зеленым шрифтом, и блок и шрифт с тенями, да еще у блока белая обводка dashed?! Не, на вкус и цвет, само собой, не хочу быть занудой и придираться к вкусовщине… Но блин!!!
— раз затронул стили — border толщина заданная в vh? Это зачем вообще?
— посмотрите пример с табами (https://materializecss.com/tabs.html) — второй блок как раз скроллится как и ваш, но выглядит по людски и управлять им легче. Я молчу уж что читсый CSS без костылей и забытых вами на каждом шагу листнеров (уже выше написали)
Ну и оффтом (статья правда не про прелоадеры была) — вот как выглядит ваша страница cmf в хроме на мобиле:
Full-R Автор
Спасибо за ценное замечание про touchpad. У меня было ориентировано на touch экраны и полноценную мышь. Хотя я вообще то на touchpad всю Ghotic 3 прошел с двумя мечами и луком.
Я бы свами согласился, что на CSS лучше, если бы вы показали годный рабочий пример.
Что до дизайна сайта моей компании — это к статье не относится. В chrome под Android у меня все прекрасно загружается и прелоадер исчезает. Дальше при промотке страницы срабатывает Lazy Load для изображений, как и задумано.
Критиковать же дизайн, который я для примера статьи не делал даже, вообще не стоит. Стили остаются на усмотрение разработчика.
Sorvs71
Полезно однако)
owwyye
Какой у вас
прекрасныйудивительный сайт! Три разных лоадера!Full-R Автор
Где вы увидели три разных лоадера? Один и тот же эффект и код используется для DOMContentLoaded и fetch запросов. И тот же самый SVG для lazy load картинок.
owwyye
Как бы вам объяснить… У вас плохо все, абсолютно все.
Full-R Автор
Это вы мне настроение перед сном испортить пытаетесь или есть что то по делу. В данном случае, если хотите покритиковать, то прошу не отвлекаться от темы статьи или хотя бы быть конструктивным. Уже хватает одного, который первым комментарием выкладывает прелые мемы с мексиканцами.
vmkazakoff
Я вот расписал выше подробнее, но ощущение, что если человек сам этого не видит, то ваш ответ был более логичный… )))
Full-R Автор
Господин мазафаков, ой, вмказакоффЪ. Какой никнейм сложновоспринимаемый. Я вам ответил выше. Предлагаю схлопнуться в одну веточку, а то полезное осязаемое пользователями пространство транжириться на 80% не относящимся к теме вопросам.
vmkazakoff
Сударь, вы неизлечимы :)
Раз медицина тут бессильна, то и я не стану мешать эволюции — авось сама разберется.
dopusteam
Вам нормальный фидбек выше дали, вы от него как то мастерски уклонились и зачем то просите ещё
owwyye
Просто человек любит обмазываться и дро… учить.
owwyye
Да, это тот самый случай. Удивительно вот что: человек вроде живет не в вакууме, пользуется (хочется верить) современными сайтами, но как у многих других айтишников напрочь лишен способности анализировать увиденное (см. дизайн программиста). Обычно это еще накладывается на брызжущее во все стороны эго и исключительную упёртость выражающиеся в репликах «индустрия свернула не туда» и тотальное отрицание типичных интерфейсных практик и паттернов.
И это все не про спагетти-код, с этим пациентом в принципе никакой диалог невозможен.
Full-R Автор
Вас бы поняли может быть, но раньше говорили проще: «Портфолио покажи». Не надо за всех отвечать и про мою неполноценность намекать. Это скорее вам надо пару курсов хорошего тона и долгую переквалификацию. Я уже в современном мире, а вы где-то как раз в заднем вакууме со своим недовольством.
owwyye
Бхахаха, а поциэнт реально упоротый.