Как и зачем я отключил свой фавикон / Хабр | Веб-студия Nat.od.ua
Как и зачем я отключил свой фавикон / Хабр
Перевод статьи “Disabling My Favicon: How and Why”
Привет Хабр! Недавно в недельной подборке я увидел эту статейку на английском языке и решил ее перевести так как она показалась мне довольно интересной. Далее перевод.
И так начнем: на моем сайте нет фавикона, и, скорее всего, его никогда не будет.
Нельзя с полной уверенностью сказать, почему я пришел к такому решению, но, учитывая тот момент, что у меня нет логотипа — и я действительно не хочу его иметь — то и создание значка для сайта мне показалось излишним.
Но есть проблемка, фавикон нельзя просто не задать, поскольку большинство веб-браузеров все равно попытаются его запросить. А это в свою очередь означает, что при каждой загрузке страницы будет повторный запрос файла favicon.ico, который определится как Not Found.
Не знаю как вас, а меня это раздражает.
И так, что же нам делать?
Самый простой способ решить эту проблему это сделать прозрачную фавиконку, но это не решает мою проблему так как я хочу избавиться от лишнего запроса а не просто получить успешный ответ при запросе.
Попытка первая
Хотя пропуск тега icon и не приводит к желаемому результату, я решил поэкспериментировать со значениями, которые он дает. Моей первой попыткой было использовать простой хэш:
В целом, хоть это и помогло устранить ответ 404 Not Found в запросе, но затем я столкнулся с другой проблемой – повторно запрошена вся страница. Это объясняется тем, что # в конечном итоге интерпретируется браузером как https://flower.codes/#, как практически и любой другой символ, с которым вы, возможно, захотите поэкспериментировать (пробелы, вопросительные знаки и т. д.).
Итог этого решения: не подходит.
Попытка вторая
И так, мы не можем просто пропустить фавикон и не можем использовать произвольный символ… Что же тогда делать?
Немного покопавшись, я наткнулся на концепцию, с которой большинство веб-разработчиков довольно хорошо знакомы: Data URLs.
Для непосвященных: URI данных позволяют встраивать файлы непосредственно в HTML, а не выполнять внешний запрос. Если максимально упростить, они обычно состоят из определения типа загружаемого файла (например, text/html или text/plain) и содержимого файла.
Это означает, что вместо загрузки внешнего файла вы можете включить его в строку следующим образом:
data:text/html,
Однако для наших целей мы можем определить Data URL, который фактически является пустым, просто опустив как тип контента, так и само содержимое:
Итог этого решения: подходит.
Хорошо, но… Зачем?
Как и ожидалось, это решило мою проблему. Включив содержимое напрямую и оставив его пустым, мы можем устранить повторный запрос, не вызывая никаких ошибок в консоли.
Но зачем мне все эти проблемы?
Хотите верьте, хотите нет, но мне на самом деле нравятся фавиконки. Я думаю, что они значительно упрощают организацию вкладок и закладок. Но во всемирной паутине, плотно заполненной ими, отказ от одного на моем собственном сайте казался маленьким способом выделиться (а пройти лишнюю милю, чтобы сделать это «правильным» способом – это просто вишенка на торте).
Если вдруг кому интересно то я веду телеграм канал по фронтенду где выкладываю интересные статьи на разные темы а так же сам периодически пишу шорт-риды которые могут быть вам полезны.
6 простых правил хорошего alt-текста / Хабр | Веб-студия Nat.od.ua
6 простых правил хорошего alt-текста / Хабр
Давайте поговорим о том, как написать хорошее описание фотографий в атрибуте alt.
В первой части мы обсудили основные правила написания alt-текста для фотографий и изображений. В этот раз поговорим о том, каким именно должно быть описание, чтобы в нём был смысл.
Альтернативный текст — это описание изображения словами. Это описание должно помогать людям, которые читают или слышат это описание, иначе оно не нужно и лучше вообще его не указывать.
Просто перескажите, что находится на картинке
Есть простой совет, чтобы создать идеальное описание.
Представьте, как вы звоните другу и рассказываете, что видите на изображении.
Но не переусердствуйте, так как лучше, если описание будет лаконичным.
Разноцветный кот есть корм и облизывается (https://unsplash.com/photos/_FlMYRBExBk)
Имя создателя и ключевые слова для поисковой оптимизации не должны находиться в описании изображения, но они могут находиться в статье.
Используйте контекст, в котором находится картинка
Одна и та же картинка может иметь разное описание в alt в зависимости от контекста. Контекст — это то, что окружает иллюстрацию, например, текст статьи, если картинка туда встроена.
Проще говоря,
Описание зависит от того, на чём сейчас нужно сфокусировать пользователя.
Посмотрите на картинку ниже и подумайте, какое описание вы бы указали в alt.
Есть несколько правильных ответов:
CSS is awesome.
Подойдет для статьи о том, как работает CSS (плохо).
Белая кружка с надписью.
Если ваша статья про кружки и никак не связана с веб-разработкой.
Бардак на столе разработчика: кружки, переплетенные провода, карточки.
Если рассказываете про жизнь во время пандемии или регулярную уборку на рабочем столе.
Белый стол.
Если продаёте столы и считаете, что то, что находится на столе, не имеет никакой разницы. Да хоть тарелка.
Белая кружка с надписью в квадрате “CSS is awesome” стоит на белом столе. Awesome выходит за пределы квадрата.
Этот вариант подойдет, если вы не понимаете контекста статьи, но нужно поставить какую-то картинку и что-то там написать. Так тоже можно делать.
Главная мысль в этих примерах такая, что вам нужно акцентировать внимание на том, что сейчас более важно для статьи в этом изображении. Чтобы определить «сейчас», нужно понимать, о чём написано в статье рядом с этим изображением. Если в статье рассказывается о том, какие виды кружек бывают, то нужно описать детали кружки на изображении, например “Белая кружка с надписью “CSS is Awesome””. Всё, что есть на фоне, в этом случае не важно.
Не пишите alt, если картинка не несёт смысла
Нет ничего плохого в пустом alt, если изображение не несёт дополнительного смысла. Например, как описать мою аватарку в интерфейсе сайта?
Для контекста: на аватарке нарисована голова человека, которые лежит на кровати и не может уснуть от копошащихся мыслей.
«Николай Шабалин» совсем плохой вариант, так как он полностью дублирует текст справа. Аватарка используется в шапке сайта, поэтому описывать её нет смысла.
Не описывать картинку нормально, но будьте аккуратнее и всегда помните про контекст.
Передавайте эмоции через alt
С первого взгляда не очевидно, что любая аватарка может передавать какие-то там эмоции, но в контексте иногда есть смысл их передать. Например, если бы статья была про бессонницу, то моей аватарке можно было бы написать alt «человек с кучей мыслей прям перед сном».
Радостный человек лежит в гамаке на закате (https://unsplash.com/photos/Kc4MGyzfOTI)
Эту картинку можно описать «Радостный человек лежит в гамаке на закате». Нужно ли писать здесь, что человек радостный, хотя мы даже не видим его лица? Это не обязательно, но можно так написать, если мы продаём гамаки, хотим дополнить картинку и сказать, насколько хорошо он себя чувствует в нашем удобном гамаке, лёжа в нём на берегу океана.
Про эмоциональное описание для изображений вы можете подробнее узнать в блоге Лео́ни Уотсон.
Не заигрывайте с декором ради декора
Все мы знаем, что декоративные изображения обычно используются без описания, хотя такая возможность есть. Для этого можно использовать свойства content: “” или, если вы используете изображение в content: url(), то правильно будет написать так:
content: url(“path/to/image.jpg” / “Описание декоративного изображения”).
Но лучше использовать этот способ пореже, так как чаще всего декоративные изображения бессмысленны. Но если по какой-то причине вам хочется передать эмоции декоративного изображения, то такая возможность есть.
Не пишите в alt названия файлов или имя фотографа
Я часто смотрю, какое описание заполняют в атрибуте alt, и удивляюсь, насколько фантазия может пойти не туда. Часть заполненный альтернативный текст полностью бесполезен, так как не передаёт смысла изображения.
Какое описание не подойдёт:
imgX2 или 7e0273c07fef3b598590d3fdddf7808604f0a191435c2c0c — это просто название или хэш файла с картинкой.
название сайта или название статьи — возможно, это проделки для SEO.
имя фотографа — тут всё понятно.
Все эти варианты никак не помогут в основной задаче альт-текста — понять, что было на картинке, которая не загрузилась, или при чтении страницы скринридером.
Итого — как написать хороший alt-текст
Просто понятное описание — самое хорошее решение, если оно не слишком длинное. Отсутствие описания — нормально, но должно быть к месту. Эмоциональное описание — тоже хорошо, если вписано в контекст.
А вот шесть правил коротко:
Просто расскажите, что видите на картинке — чем короче, тем лучше.
Используйте контекст, в котором находится картинка — то есть учитывайте, что находится вокруг неё на сайте.
Не пишите alt, если картинка не несёт отдельного смысла — например, аватарка в профиле.
Не стесняйтесь передавать эмоции через alt — если кто-то радуется или грустит, напишите об этом, если уместно.
Не пишите alt у декоративных изображений — например, у иконок.
Не пишите в alt название файла или имя фотографа — alt нужен не для этого.
Экспериментируйте, пробуйте и не бойтесь ошибиться.
Как с помощью Python создать приложение для расшифровки речи в реальном времени | Веб-студия Nat.od.ua
Как с помощью Python создать приложение для расшифровки речи в реальном времени
Научить ИИ разговаривать шёпотом — непростая задача даже сегодня. Но мы покажем, насколько простыми стали распознавание и транскрипция речи, по крайней мере, на поверхности. Интересно? Тогда добро пожаловать под кат.
Материал подготовлен к старту курса по Fullstack-разработке на Python.
Введение
Приложение расшифровки речи в режиме реального времени автоматически преобразует текст в речь. Этот текст почти мгновенно отображается на экране, а использовать подобные приложения можно для самых разных целей, включая расшифровку лекций, конференций и встреч. Здесь есть ряд преимуществ:
можно сразу записывать идеи и беседы. Это очень полезная функция для людей, которые работают в быстро меняющейся среде, и для людей с большим количеством идей;
развивать навыки общения, ведь теперь вы увидите, как говорите вы сами и как говорят другие.
Такими приложениями могут пользоваться люди с нарушением слуха или те, кто учит английский. Приложение расшифровывает аудио в реальном времени, а пользователь видит текст на экране параллельно произношению слов. К тексту можно применить обработку естественного языка.
Мы научимся создавать приложение для динамического преобразования речи в текст и сделаем это с помощью API AssemblyAI (серверная часть) и Streamlit (клиентская часть).
Вот видеоверсия статьи:
Обзор приложения
Для приложения понадобятся следующие библиотеки для Python:
streamlit — веб-фреймворком воспользуемся для размещения всех виджетов ввода и вывода;
websocket — позволяет приложению взаимодействовать с API AssemblyAI;
asyncio — позволяет выполнять всевозможный речевой ввод и вывод асинхронно;
base64 — кодирует и декодирует аудиосигнал перед его отправкой в API AssemblyAI;
json — считывает речевой вывод, сгенерированный через API AssemblyAI (например, расшифрованный текст);
pyaudio — обрабатывает речевой ввод через библиотеку PortAudio;
os и pathlib — используются для перехода по различным папкам проекта и работы с файлами.
Настройка рабочей среды
Чтобы воссоздать приложение для динамической расшифровки речи на вашем компьютере, мы создадим среду conda под названием transcription:
conda create -n transcription python=3.9
Возможно, высветится запрос на установку зависимостей Python-библиотеки. Если так, нажмите клавишу Y, чтобы подтвердить действие и продолжить.
После создания среды conda активировать её можно так:
conda activate transcription
Делать это нужно каждый раз в начале написания кода, а по завершении работы с кодом из среды нужно выйти:
conda deactivateЗагрузка GitHub-репозитория
Загрузим с GitHub весь репозиторий приложения динамической расшифровки речи:
git clone https://github.com/dataprofessor/realtime-transcription
Переходим в папку realtime-transcription:
cd realtime-transcription
Можно установить обязательные библиотеки, которыми пользуется приложение:
pip install -r requirements.txtПолучение ключа от API AssemblyAI
Получить доступ к API в AssemblyAI крайне просто. Для начала зарегистрируйтесь на AssemblyAI, это бесплатно. Зайдите в учётную запись. На панели справа вы увидите API-ключ:
Как получить API-ключ в AssemblyAI
Теперь, когда вы скопировали API-ключ в память, необходимо добавить его в файл secrets.toml из папки.streamlit. Путь к файлу выглядит так: .streamlit/secrets.toml. Его содержание должно быть таким:
api_key = ‘xxxxx’
Вместо xxxxx вставьте свой ключ от API. Получить этот ключ мы сможем с помощью строки кода st.secrets.
Запуск приложения
Прежде чем запускать приложение, давайте рассмотрим содержимое рабочей директории (то, что открывается по команде tree в Bash):
Содержимое папки realtime-transcription
Теперь мы готовы запустить своё приложение:
streamlit run streamlit_app.py
Этот код позволит открыть приложение в новом окне браузера:
Скриншот приложения
Посмотрим, как работает приложение:
Работа приложения
Объяснение кода
Ниже объясняется базовый код приложения.
Строки 1–8 — импорт обязательных библиотек веб-приложения.
Строки 10-13 — начальное состоянии сессии приложения.
Строки 15-22 — ввод для приём пользовательского ввода параметров аудио представлены виджетом text_input.
Строки 24-31 — для открытия потока аудиоданных через pyaudio используются входные параметры аудио из блока кода выше.
Строки 33-46 — определяют 3 пользовательские функции (например, start_listening, stop_listening и download_transcription), которые будут вызываться в коде (см. ниже).
Строка 49 — отображает название приложения через строку st.title.
Строки 51–62 — отображает информацию о приложении (раздел About) с помощью строки st.expander.
Строки 64–67 — создают 2 столбца строкой st.columns для размещения кнопок «Пуск» (Start) и «Стоп» (Stop). То есть они используют start_listening и stop_listening через параметр on_click виджета кнопки.
Строки 69–139 — здесь выполняется обработка речевого входа и выхода: аудиосигнал передаётся в API AssemblyAI, где расшифрованный текст выдаётся в формате JSON. Эта часть была изменена и адаптирована из блока кода, написанного Мисрой Турп и Джорджиосом Мириантусом.
Строки 141–144 — отображают кнопку загрузки расшифровки, а затем удаляют файл.
Весь код
import websockets
import asyncio
import base64
import json
import pyaudio
import os
from pathlib import Path
# Состояние сессии
if ‘text’ not in st.session_state:
st.session_state = ‘Listening…’
st.session_state = False
# Параметры аудио
st.sidebar.header(‘Audio Parameters’)
FRAMES_PER_BUFFER = int(st.sidebar.text_input(‘Frames per buffer’, 3200))
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = int(st.sidebar.text_input(‘Rate’, 16000))
p = pyaudio.PyAudio()
# Открываем аудиопоток с указанными выше параметрами
stream = p.open(
format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=FRAMES_PER_BUFFER
)
# Запуск и остановка прослушивания
def start_listening():
st.session_state = True
def download_transcription():
read_txt = open(‘transcription.txt’, ‘r’)
st.download_button(
label=”Download transcription”,
data=read_txt,
file_name=”transcription_output.txt”,
mime=”text/plain”)
def stop_listening():
st.session_state = False
# Веб-интерфейс (фронтенд)
st.title(‘🎙️ Real-Time Transcription App’)
with st.expander(‘About this App’):
st.markdown(”’
This Streamlit app uses the AssemblyAI API to perform real-time transcription.
Libraries used:
– `streamlit` – web framework
– `pyaudio` – a Python library providing bindings to (http://www.portaudio.com/) (cross-platform audio processing library)
– `websockets` – allows interaction with the API
– `asyncio` – allows concurrent input/output processing
– `base64` – encode/decode audio data
– `json` – allows reading of AssemblyAI audio output in JSON format
”’)
col1, col2 = st.columns(2)
col1.button(‘Start’, on_click=start_listening)
col2.button(‘Stop’, on_click=stop_listening)
# Отправляем аудио (ввод)
async def send_receive():
URL = f”wss://api.assemblyai.com/v2/realtime/ws?sample_rate={RATE}”
print(f’Connecting websocket to url ${URL}’)
async with websockets.connect(
URL,
extra_headers=((“Authorization”, st.secrets),),
ping_interval=5,
ping_timeout=20
) as _ws:
r = await asyncio.sleep(0.1)
print(“Receiving messages …”)
session_begins = await _ws.recv()
print(session_begins)
print(“Sending messages …”)
async def send():
while st.session_state:
try:
data = stream.read(FRAMES_PER_BUFFER)
data = base64.b64encode(data).decode(“utf-8”)
json_data = json.dumps({“audio_data”:str(data)})
r = await _ws.send(json_data)
except websockets.exceptions.ConnectionClosedError as e:
print(e)
assert e.code == 4008
break
except Exception as e:
print(e)
assert False, “Not a websocket 4008 error”
r = await asyncio.sleep(0.01)
# Принимаем транскрипцию (вывод)
async def receive():
while st.session_state:
try:
result_str = await _ws.recv()
result = json.loads(result_str)
if json.loads(result_str)==’FinalTranscript’:
print(result)
st.session_state = result
st.write(st.session_state)
transcription_txt = open(‘transcription.txt’, ‘a’)
transcription_txt.write(st.session_state)
transcription_txt.write(‘ ‘)
transcription_txt.close()
except websockets.exceptions.ConnectionClosedError as e:
print(e)
assert e.code == 4008
break
except Exception as e:
print(e)
assert False, “Not a websocket 4008 error”
send_result, receive_result = await asyncio.gather(send(), receive())
asyncio.run(send_receive())
if Path(‘transcription.txt’).is_file():
st.markdown(‘### Download’)
download_transcription()
os.remove(‘transcription.txt’)
# Ссылки (этот код — адаптация кода по ссылкам ниже)
# 1. https://github.com/misraturp/Real-time-transcription-from-microphone
# 2. https://medium.com/towards-data-science/real-time-speech-recognition-python-assemblyai-13d35eeed226
Заключение
Поздравляем, вы создали приложение для динамического преобразования речи на Python с помощью API AssemblyAI. Как уже говорилось, у таких приложений есть несколько вариантов использования (диктовка статьи/сочинения или письма, развитие навыков общения преобразование речи для людей с нарушением слуха и т. д.).
Вы можете поступить как автор, то есть снова адаптировать код, уже для российских голосовых API. А мы поможем прокачать ваши навыки или с самого начала освоить профессию, актуальную в любое время:
Выбрать другую востребованную профессию.
способы сделать сайт послушным / Хабр | Веб-студия Nat.od.ua
способы сделать сайт послушным / Хабр
Вёрстка помогает нам выстраивать содержимое веб-страниц по определённым правилам: например, строго в соответствии с согласованным макетом или в зависимости от пользовательского устройства. Сегодня сайты неплохо умеют подстраивать свой контент и под различные размеры экранов, но так было не всегда.
Существует несколько основных подходов к вёрстке, особенности которых мы рассмотрим в этой статье на простом наглядном примере:
Структура представленной разметки страницы очень простая: сверху располагается шапка, ниже — основное содержимое страницы, которое включает в себя контейнер для карточек и сами карточки.
Для демонстрации подходов к вёрстке мы будем вносить изменения только в CSS, в то время как представленная выше HTML-разметка будет оставаться неизменной.
А теперь давайте вернёмся к обсуждению самих подходов.
Фиксированная вёрстка
Самым простым и не требующим больших усилий решением можно считать фиксированную вёрстку. Фиксированной она называется потому, что содержимое страницы, свёрстанной таким образом, никак не подстраивается под размер экрана и отображается на всех устройствах одинаково.
Для фиксированной вёрстки ширина контейнера, в котором находится весь контент, указывается в абсолютных величинах (например, px). Помимо ширины контейнера не лишним будет указать внешние отступы, чтобы контент отображался всегда по центру экрана.
Стили, указанные после отступа, нужны для формирования сетки контента, которая будет иметь три колонки одинаковой ширины и отступы в 24px между колонками и строками. Сетка задаётся с помощью CSS Grid Layout, и если вы ещё не знакомы с возможностями этого способа раскладки, рекомендую ознакомиться с данной статьёй.
.container {
width: 900px;
margin: 24px auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
Этого уже будет достаточно, чтобы добиться ожидаемого отображения веб-сайта на обычном экране компьютера.
Однако, такая вёрстка совершенно не подходит для отображения на мобильных устройствах: из-за фиксированной ширины контейнера пользователю придётся приближать страницу и скроллить её влево-вправо, чтобы иметь возможность прочитать имеющийся на ней текст.
Отзывчивая вёрстка
В таком случае мы могли бы сделать наш интерфейс чуть более дружелюбным, если бы стали использовать отзывчивую (или резиновую) вёрстку. Её особенность состоит в том, что для задания параметров используются не абсолютные, а относительные величины (например, %), благодаря чему содержимое страницы может подстраиваться под ширину экрана.
Для нашего примера также необходимо указать максимальную ширину контейнера, чтобы на больших экранах контент отображался точно так же, как и в случае с фиксированной вёрсткой.
Ещё мы добавим внутренний отступ, чтобы на маленьком экране карточки не прилипали к его краям. Именно из-за этих внутренних отступов в 16px слева и справа ограничения по максимальной ширине увеличиваются с 900px до 932px.
.container {
width: 100%;
max-width: 932px;
padding: 0 16px;
margin: 24px auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
Вот как поведёт себя резиновая вёрстка на средних и маленьких экранах. На планшете она отображается так же хорошо, как и на экране компьютера, но на мобильных устройствах ситуация только ухудшилась: карточки стали настолько узкими, что прочитать текст на них практически невозможно.
Адаптивная вёрстка
С ростом популярности смартфонов появилась и стала распространённой адаптивная вёрстка, которая позволила изменять стили контейнера в зависимости от ширины экрана. Этот подход может помочь нам избежать проблем, с которыми мы столкнулись выше, когда использовали резиновую вёрстку на маленьком экране.
С помощью медиа-запросов мы можем изменять сетку, по которой будет выстраиваться контент. Изначально стили указываются для минимальных размеров экрана (такой подход называется mobile-first), и если ширина экрана достигает какого-либо брейкпоинта, первоначальные стили заменяются новыми. Существует множество систем величин брейкпоинтов, но мы воспользуемся значениями, представленными здесь.
В нашем примере мы будем изменять максимальную ширину контейнера и сетку для расположения карточек: на маленьких экранах (шириной меньше 480px) колонка будет одна, на экранах средней величины (от 480px до 768px) — две, а на больших (шириной больше 768px) — три. Максимально возможная ширина контейнера по-прежнему 932px, она будет установлена на экранах, ширина которых равна этому значению или больше него.
.container {
max-width: 300px;
padding: 0 16px;
margin: 24px auto;
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 24px;
}
@media screen and (min-width: 480px) {
.container {
max-width: 480px;
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 768px) {
.container {
max-width: 768px;
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (min-width: 932px) {
.container {
max-width: 932px;
}
}Отзывчиво-адаптивная вёрстка
Последним и самым современным подходом к вёрстке веб-сайтов является отзывчиво-адаптивная вёрстка, которая сочетает в себе положительные свойства двух рассмотренных выше подходов. Такая вёрстка подойдёт в том случае, когда приоритетная задача состоит в том, чтобы занять максимум от имеющегося пространства: например, если контент представлен не карточками, а изображениями, и нам не нужны поля слева и справа от контейнера.
При таком подходе вёрстка между брейкпоинтами ведёт себя абсолютно «резиново», но на брейкпоинтах могут меняться какие-то важные свойства отображения (в нашем случае — свойства сетки). Используя данный подход, вы можете быть уверены, что на экране абсолютно любого размера ваше приложение будет отображаться как нужно и им будет удобно пользоваться.
.container {
width: 100%;
max-width: 932px;
padding: 0 16px;
margin: 24px auto;
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 24px;
}
@media screen and (min-width: 480px) {
.container {
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 768px) {
.container {
grid-template-columns: repeat(3, 1fr);
}
}
Последний рассмотренный подход к вёрстке считается самым надёжным и безопасным решением, однако, выбор типа вёрстки должен производиться индивидуально в зависимости от задач и структуры конкретного веб-приложения.
Код всех рассмотренных примеров вы можете посмотреть в песочнице.
В этой статье мы рассмотрели типы вёрстки, отличающиеся поведением на экранах разных размеров, но это лишь один взгляд на вёрстку. Существуют также иные подходы к выделению её типов, например, в зависимости от используемых HTML-тегов, но об этом мы поговорим уже в следующих статьях.
Стили заголовков в CSS: градиенты / Хабр | Веб-студия Nat.od.ua
Стили заголовков в CSS: градиенты / Хабр
Заголовки должны быть большими, жирными и громкими, чтобы привлекать внимание пользователя за несколько секунд. Мы перевели статью о стилях заголовков, сегодня предлагаем изучить градиенты.
В заголовках обычно содержится текст, набранный крупным шрифтом, выделенный начертанием или цветом. К нему иногда прилагается описательный подзаголовок, изображения и кнопки призыва к действию.
1 — заголовок, 2 — описание, 3 — изображение, 4 — кнопки
Текст основного заголовка обозначается тегом h1, его стилизацией мы и займёмся.
Полезные подсказкиВыбирайте правильные цвета
Если работаете с брендбуком, сосредоточьтесь на фирменных цветах. Если инструкций нет, сначала подберите сочетающиеся цвета, потом приступайте к оформлению.
Делайте заголовок уникальным
Тест и стиль должны быть уникальными. На одной веб-странице нужен только один тег h1 — это ради SEO и производительности, особенно если у заголовка сложный стиль.
Выбирайте правильные шрифты
Между визуальным хаосом и хорошим заголовком — тонкая грань, которую пересекают размер, цвет, начертание и гарнитура шрифта. Поэтому иногда творческие порывы надо притормаживать.
CSS градиенты цвета текста
Градиент — цветовой переход между цветами, при котором они смешиваются. Есть три способа добавить цветовые градиенты к тексту заголовка CSS:
linear-gradient()
radial-gradient()
conic-gradient()
1. linear-gradient()
Функция обеспечивает переход цвета по прямой линии. Сделаем для старта заголовок в HTML:
Modern Frontend Monitoring and Product Analytics
Используйте семантический тег header, как показано, тег section с ID или проверенный с class=”header”. Главное, чтобы заголовок был заметным и узнаваемым.
Функция linear-gradient() позволяет добавлять специфические параметры, в том числе направление градиента и как минимум два значения цвета. Но можно добавлять больше — количество цветов не ограничено. Посмотрим на CSS:
header h1 {
font-size: 70px;
font-weight: 600;
background-image: linear-gradient(to left, #553c9a, #b393d3);
color: transparent;
background-clip: text;
-webkit-background-clip: text;
}
Свойство background-clip гарантирует, что фон не выйдет за пределы элемента — в данном случае это текста. Свойство color установлено как transparent, так что фон видно прямо за заголовком. Результат кода: светлый цвет перетекает в тёмный слева направо:
Линейный градиент текста слева направо
Стилизовать заголовок с градиентом можно в других направлениях:
Линейные градиенты текста с разными направлениями
Градиент может быть и диагональным:
linear-gradient(to bottom left, #553c9a, #ee4b2b);
Направление можно указать с помощью углов:
linear-gradient(45deg, #553c9a, #ee4b2b);Линейный градиент по диагонали
Начало перехода цвета можно задать, добавив процентное значение после первого цвета. Фиолетовый занимает 45% текста заголовка, а потом переходит в красный:
linear-gradient(to right, #553c9a 45%, #ee4b2b)Линейный градиент с процентами2. radial-gradient()
Радиальный градиент начинается в исходной точке, из которой расходятся цвета. По умолчанию переход цвета начинается из центра элемента.
background-image: radial-gradient(#553c9a, #ee4b2b);
Указать направление с помощью radial-gradient() нельзя, но зато можно указать форму: круг или эллипс.
radial-gradient(circle, #553c9a, #ee4b2b);
radial-gradient(ellipse, #553c9a, #ee4b2b);Радиальный градиент
Для изменения положения градиента есть четыре параметра:
closest-side
closest-corner
farthest-side
farthest-corner
3. conic-gradient()
Этот градиент тоже начинается с исходной точки. Вокруг неё вращается цветовой переход. Для наглядности добавим к заголовку третий цвет.
background-image: conic-gradient(#553c9a, #ee4b2b, #00c2cb);
Результат:
Пример конического градиента
Грубую начальную точку, где бирюзовый переходит в фиолетовый на букве n, можно сгладить. Для этого добавим первый цвет в конец функции.
conic-gradient(#553c9a, #ee4b2b, #00c2cb, #553c9a);
После цветов можно добавить процентные значения, чтобы контролировать точки начала цветовых переходов.
conic-gradient(#553c9a 30%, #ee4b2b 40%, #ee4b2b 70%, #00c2cb 80%, #553c9a);
Итог:
Функцию можно использовать, чтобы сделать градиент, который градиентом не будет.
conic-gradient(
#553c9a 0%, #553c9a 33%, #ee4b2b 33%, #ee4b2b 66%, #00c2cb 66%, #00c2cb 99%
);
Первый цвет перемещается от начальной точки 0% и проворачивается на 33% заголовка. В этой точке должен начаться цветовой переход, но начальная точка следующего цвета была 33%, поэтому цвет меняется сразу.
Бонус: повторяющиеся градиенты
Как можно понять, градиент цвета текста повторяется по всему заголовку. Добавим цветовые точки, получим градиентные узоры.
repeating-linear-gradient(to right, #553c9a, #ee4b2b 10%, #553c9a 20%);
repeating-radial-gradient(circle closest-corner at 100px 100px, #553c9a, #ee4b2b 10%, #553c9a 20%);
repeating-conic-gradient(#553c9a 0%, #553c9a 10%, #ee4b2b 10%, #ee4b2b 20%,
#00c2cb 20%, #00c2cb 30%);
Даже если вы не собираетесь вызывать у пользователей головокружение, учитесь использовать градиенты.
Используйте оттенки одного цвета
Чтобы получить паттерн repeating-radial, используем такой синтаксис:
repeating-radial-gradient(circle closest-corner at 100px 100px, #553c9a, #ee4b2b 10%, #553c9a 20%);
Теперь поменяем красный на светло-фиолетовый:
repeating-radial-gradient(circle closest-corner at 100px 100px, #553c9a, #b393d3 10%, #553c9a 20%);
Итог:
Используйте не только оттенки одного цвета. Жёлтый хорошо сочетается с оранжевым, а зелёный — с синим.
Используйте правильные параметры
Помните четыре параметра для radial-gradient? Посмотрим ещё.
repeating-radial-gradient(
Градиенту можно задать форму circle или ellipse. По умолчанию это ellipse с положением в центре.
Размер фигуры можно определить с помощью параметров:
closest-side — форму градиента определяет ближайшая к центру стороной элемента;
closest-corner — форму градиента определяет ближайший угол элемента;
farthest-side —форму градиента определяет сторона элемента, удалённая от центра;
farthest-corner — форму градиента определяет удалённый от центра угол.
Далее идёт позиция от corner или side. Это может быть процентное значение или длина.
Используйте минимум два цвета, но помните, что повторение первого в качестве третьего даёт плавный переход. Для ясности используем красный и фиолетовый:
Вот что получится при repeating-radial-gradient использовании значений closest-:
А теперь при использовании значений farthest-:
Между двумя первыми заголовками нет большой разницы, потому что градиент расходится из центра наружу. Тот же результат можем получить, используя linear-gradient. Лучше указать форму и определить точки цветовых переходов.
Видно, что свойство repeating-radial-gradient со значением closest-side или closest-corner выглядит ярче, но снижает читаемость, если использовать неподходящие цвета. Значения farthest-corner или farthest-side позволяют получить чёткий текст даже с контрастными цветами.
Фронтенд-новости №8. Вышел WordPress 6.0, найдена оптимальная длина строки, под вопросом | Веб-студия Nat.od.ua
Фронтенд-новости №8. Вышел WordPress 6.0, найдена оптимальная длина строки, под вопросом
Дайджест новостей из мира фронтенд-разработки за последнюю неделю 23–29 мая.
К сожалению, на хабре нельзя устанавливать alt для картинок, поэтому …ДоступностьHTMLCSSJavaScriptReactNode.jsAngularVueИнструментыОбщее
Как выделяться с помощью типографики. Рассматривайте возможности типографики как дополнительную возможность привлечь внимание вашего интерфейса.
Оптимальная длина строки. Спойлер: 50-75 символов. Меньше 50 или больше 75 может ухудшить читабельность
Почему в вебе закругляют углы?
Как читать статьи на английском языке
В дайджесте много статей и видео на английском языке, чтобы это не стало препятствием: в Google Chrome есть функция перевода страницы с любого популярного языка, а видео можно перевести в Яндекс Браузере.
Какие шрифты использовать на сайте? / Хабр | Веб-студия Nat.od.ua
Какие шрифты использовать на сайте? / Хабр
Для того чтобы создать сайт, который будет максимально привлекателен для посетителя, мало купить оригинальное доменное имя, разработать уникальный дизайн и вставить яркие картинки. Для сайта не менее важным элементом являются используемые шрифты, являющиеся частью дизайна. Если веб шрифты страницы, заголовков и текстовых блоков правильно подобраны, то прочтение основного текста будет более простым и комфортным, а это положительно скажется на общем впечатлении о сайте. В большинстве случаев именно текст является одним из главных способов коммуникации посетителя сайта и компании, поэтому важно понимать, что шрифты для инстаграма или для фотошопа далеко не всегда будут логично смотреться на информационном портале или в Интернет-магазине.
Виды шрифтов для сайта
Все веб шрифты страницы сайта делятся по начертанию на рукописные и печатные. Кириллические рукописные в большинстве случаев используются для того, чтобы оформить привлекательный заголовок, логотип, выделить в тексте отдельные фразы. Также такие шрифты подойдут для инстаграма и для фотошопа, когда необходимо персонализировать картинку. Для основных текстовых блоков такие шрифты не подходят, так как не особо удобны для чтения и восприятия, поэтому для основного текста используются печатные шрифты. Они делятся на три категории:
С засечками (в их названии часто присутствует serif) – они хорошо подходят для печатных изданий, а для сайта не совсем логично их использовать, так как они не всегда удобны к восприятию и рассеивают внимание посетителя. Поэтому такие шрифты востребованы для заголовков, подзаголовков и выделения отдельных блоков;
Рубленые или без засечек (обозначаются как sans serif) – нейтральные и универсальные шрифты, которые удобны к прочтению и восприятию. Многие сайты оформляются только при помощи таких шрифтов;
Декоративные – отличаются оригинальностью и яркостью, но текст, который ими написан, сложен для чтения, поэтому чаще всего такие шрифты применяются при создании заголовков и логотипов. Стоит сказать, что далеко не всегда можно скачать кириллицу отдельных шрифтов, из-за чего имеется ряд ограничений по их использованию.
Наиболее востребованные шрифты для сайтов
Количество популярных бесплатных css на просторах Интернета достаточно большое, равно как и количество шрифтов, которые доступны для бесплатного скачивания. Найти жирный (bold) шрифт не составляет труда, но необходимо знать о самых популярных шрифтах, использование которых практически всегда оправдано. Среди таких шрифтов (они все могут быть кириллическими рукописными) можно выделить:
Roboto – наиболее популярный шрифт, имеющий громадное количество вариаций, среди которых можно найти жирный (bold), light, black;
Open Sans – наиболее удобочитаемый всего читать, поэтому в Интернете его можно встретить почти на каждой интернет-странице;
Montserrat – простой, интересный и привлкательный для чтения, но при этом позволяющий «зацепиться» взгляду во время просмотра больших документов;
Roboto Condensed – имеет особенность – он узкий и несколько вытянутый, но при этом понятный и читабельный;
Source Sans Pro – имеет максимальный набор начертаний, причем разработан Adobe, поэтому отлично подойдет для Фотошопа и других графических приложений. Скачать кириллицу для него можно во всех вариантах;
Oswald – чаще всего применяется для заголовков, как для полноценных сайтов, так и для приложений;
Merriweather – имеет засечки и немного вытянутые и плавные буквы. При желании можно скачать вариант и без засечек;
Noto Sans – отличается упрощенным дизайном символов, но при этом очень хорошо читаем, благодаря чему отлично походит для текстовых материалов;
Yanone Kaffeesatz – имеет стиль типографской печати кофеен начала 20-го века. Немного старомоден, но выглядит дорого, благодаря чему отлично подходит для заголовков рекламного характера;
Caveat – имеет элегантные прописные литеры, которые узнать довольно просто. Хорошо подойдет для заголовков, но для текста.
Естественно, количество шрифтов, которые могут использоваться в популярных бесплатных css, исчисляется десятками тысяч, поэтому выбрать обычно довольно сложно. Особенно в тех случаях, если имеется желание выделиться. В такой ситуации оптимальным вариантом будет обращение к профессиональному дизайнеру, который подберет набор шрифтов с учетом дизайна сайта, его специфики и количества текстовой информации, размещенной на нем.
Особенности выбора шрифтов для сайта
Интересно то, что по статистике пользователи перед тем, как прочитать текстовые блоки на сайте, бегло просматривает заголовки, после чего принимают решение о дальнейшем прочтении, поэтому важно уделить особое внимание выбору шрифтов и их сочетанию. Использование традиционных шрифтов – Open Sans, Roboto, Montserrat – это беспроигрышный вариант, так как они простые, читабельные, привычные. Поэтому важно учесть то, какой контент необходимо оформить.
Отдельного внимания заслуживает тематика сайта и стилистика шрифтов, поэтому при возникновении сомнений относительно выбора стоит доверить этот процесс профессиональному дизайнеру. Среди общих рекомендаций по выбору шрифтов можно отметить:
Не стоит применять более трех шрифтов на странице, иначе текст никто не будет читать. Если необходимо выделить отдельные фрагменты, то имеет смысл использовать один и тот же шрифт, но в разных начертаниях;
Читабельность на первом месте – шрифт должен быть, прежде всего, читабельным, иначе страница будет только просматриваться и закрываться;
Шрифтовые пары должны сочетаться между собой. Если шрифты плохо сочетаются между собой, то стоит один из них сразу же менять;
Логичность обязательна – цвета букв должны быть подобраны с учетом фона, а размер быть таким, чтобы его было удобно читать.
Независимо от того, для какого сайта подбираются шрифты, необходимо экспериментировать – так можно подобрать оптимальное и при этом оригинальное сочетание, благодаря которому посетитель прочитает все от начал до конца, ведь именно это важно для многих.
Знакомство с профилировщиком производительности вашего браузера / Хабр | Веб-студия Nat.od.ua
Знакомство с профилировщиком производительности вашего браузера / Хабр
Эта статья — перевод оригинальной статьи Thomas Belin “Get to know your browser’s performance profiler”
Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.
Вступление
В какой-то момент своей карьеры вы, возможно, просматривали вкладку «Производительность» в инструментах разработки вашего любимого браузера. В конце концов вы попытались создать profile, но, вероятно, быстро разочаровались. Высокая плотность отображаемой информации делает ее немного подавляющей и несколько пугающей. Я был там, я понимаю тебя!
Хорошая новость: кривая обучения на самом деле не такая крутая!
Как только вы усвоите несколько концепций, он внезапно станет вашим самым ценным инструментом для устранения узких мест в производительности.
Эта статья даст вам несколько ключей к пониманию того, как работает профайлер и как правильно его использовать.
Давайте полностью забудем о console.log и console.time, сегодня мы погрузимся в профилировщик производительности!
Примечание: я не буду слишком углубляться в сложные сценарии, но в конечном итоге напишу дополнительную статью о продвинутых методах.
Модель данных
Первым шагом, который я предпринял, чтобы понять, как работает профилировщик, было чтение документации Mozilla об их новом профилировщике производительности (это отличный документ, прочтите его).
Первый вау-эффект, который у меня был, был, когда я увидел модель данных, которую использовал профайлер. Это на самом деле довольно просто
В документации Mozilla модель данных представлена следующим образом:
A A A
| | |
v v v
B B B
|
v
C
A, B и C — имена функций, а по оси X мы получаем время. По умолчанию профилировщик Firefox и Chrome настроен на создание снимка каждые 1 мс, что означает, что здесь каждый столбец представляет 1 мс.
В этом примере это означает, что стек со временем развивался таким образом.
в 0мс A вызывал B, а B все еще работал;
в 1 мс B вызывал C, а C все еще работал;
в 2 мс C закончил свою работу, и мы вернулись в B;
в 3 мс стек оказался пуст.
Из этого профилировщик может сделать следующие выводы:
А почти сразу вызвал B;
Мы пробыли ~1 мс в B, прежде чем вызвать C;
C потребовалось ~ 1 мс для выполнения;
B снова потребовалось еще ~ 1 мс после вызова C;
А закончил выполнение сразу после вызова B.
Имея в виду эту модель, мы можем создать некоторые данные:
A A A A A A A A A
| | | | | | | | |
V V V V V V V V V
B B B B B B B B B
|
V
C
B занимает немного времени до и после вызова C. Мы потратили ~1 мс на C и не потратили время на A:
A A A A A A A A A
| |
V V
B B
|
V
C
A занимает некоторое время перед вызовом B. B и C занимают ~1 мс.
Ограничения этой модели
Поскольку профилировщик берет только 1 выборку в мс, это означает, что вызов функции, который занимает менее 1 мс, имеет высокую вероятность не отображаться в сгенерированном профиле.
Представим следующий сценарий:
function A() {
B(); // < takes 0.5ms // snapshot #1
C(); // < takes 0.4ms
D(); // < takes 0.2ms // snapshot #2
E(); // < takes 0.5ms
}
Сгенерированный профиль, скорее всего, будет выглядеть примерно так:
A A
| |
v v
B D
В этом профиле не будет упоминаний о C или E.
Но мы здесь для отладки долгих задач, помните? Нет необходимости разбирать эти быстро выполняющиеся функции. Нам до них нет дела!
Собственное время (self time) против общего времени (total time)
Одно немного запутанное понятие в профилировщике — это собственное и общее время.
Однако на самом деле это понятие довольно легко понять.
Их можно определить так:
собственное — время, проведенное в самой функции;
общее — время, проведенное в функции и всех дочерних функциях, которые она вызывает.
Чтобы прочувствовать это, вот конкретный пример:
function superExpensive() {
for (let i = 0; i < 1e10; i++) {
console.log(i);
}
}
function main() {
superExpensiveComputation(); // < takes 1000ms
for (let i = 0; i < 1e6; i++) {
// ^ takes 5ms
console.log(i);
}
}
main будет иметь собственное время 5мс, но общее время 1005мс. superExpensiveComputation будет иметь общее и собственное время 1000мс
Общее время помогает выявить проблемные части кода, а собственное время позволяет сузить область поиска до функции, которая действительно требует вашего внимания.
Погружение в UI
Имея в виду эту модель, пользовательский интерфейс начинает обретать смысл. Понятия, которые мы видели ранее, начинают пригодиться для эффективного использования пользовательского интерфейса.
Здесь я сосредоточусь на профилировщике Firefox, но те же принципы применимы и к профилировщику Chrome.
Определение долгих функций верхнего уровня: дерево вызовов
Для начала возьмем очень простой пример кода. Представьте, что где-то есть кнопка, и при нажатии на нее мы запускаем функцию calculateNumber.
function generateNumber(nbIterations) {
let number = 0;
for (let i = 0; i < nbIterations; i++) {
number += Math.random();
}
return number;
} function computeNumber() {
console.log(generateNumber(1e9));
}
Вот что мы получим в нашем отчете профилировщика:
- Поскольку профилировщик фактически профилирует все процессы Firefox, мы хотим убедиться, что мы просто проверяем текущее веб-приложение, над которым работаем.
- Мы здесь веб-разработчики, нам не нужны внутренние трассировки стека браузера, давайте оставим только трассировки стека JS.
- Мы ясно видим, что больше всего времени мы тратим на функцию generateNumber (здесь функция появилась в 488 выборках, что означает, что она выполнялась как минимум 488мс).
Дерево вызовов позволит вам быстро определить, какие функции верхнего уровня требуют времени. Это хороший обзор того, с чего начать копать, но он не поможет вам быстро определить вложенные функции, которые имеют большое собственное время работы.
Определение долгих вложенных функций: инвертирование стека вызовов
Теперь рассмотрим следующее:
function computeMultipleNumbers() {
let number = 0;
for (let i = 0; i < 10; i++) {
const fnName = `gen${Math.round(Math.random() * 100)}`; // We create a function with a random name
const fn = new Function(`function ${fnName}() {
return generateNumber(1e7);
} return ${fnName}`);
number += fn()();
}
result.innerText = number;
}
Особенность этой функции в том, что она генерирует именованные функции со случайными именами. Это означает, что теперь generateNumber будет вызываться из множества различных функций.
Посмотрим, как выглядит результат:
Здесь мы видим, что вызывается много функций, но все они имеют пустое собственное время. А это значит, что это не та функция, на которую мы на самом деле потратили время, они ждали завершения чего-то другого.
Теперь, если мы инвертируем стек.
Тут становится понятно, где мы собственно потратили время: в функции generateNumber 🙂
Инверсия фактически сортирует функцию с наибольшим собственным временем и сглаживает их в корне дерева. Это отличный способ идентифицировать трудоемкую функцию, и вы получаете ее стек вызовов прямо рядом с ней. При этом вы точно знаете, какая функция является проблемой и откуда она была вызвана.
Это дерево вызовов:
topLevel // self 0
first // self 0
second // self 0
third // self 10
fourth // self 7
fifth // self 8
Это инверсия стека вызовов:
third //self 10
second
topLevel
fifth // self 8
fourth
second
topLevel
fourth // self 7
second
topLevel
Таким образом, мы можем быстро определить, что мы потратили ~ 10мс на вызов third из topLevel > second
Заключение
В этой статье мы рассмотрели основные функции профайлера. Мы увидели, как использовать дерево вызовов и перевернутый стек вызовов для быстрого определения функций, требующих много времени, в вашем приложении.
Теперь эти трудоемкие функции не обязательно являются функциями, которые вам нужно оптимизировать. Проблема может заключаться в родительской функции или даже выше в дереве. Перевернутый стек вызовов дает вам хорошую отправную точку для изучения проблемной части вашего приложения.
Мы не рассмотрели здесь, что такое Flame Graph или Stack Chart, как профилировать асинхронный код или продвинутые методы, такие как маркеры. Это то, что я хотел бы осветить в следующей статье.
Архитектура фронтенда и какой она должна быть / Хабр | Веб-студия Nat.od.ua
Архитектура фронтенда и какой она должна быть / Хабр
Все мы знаем про, или слышали про практики и паттерны проектирования SOLID, GRASP, MVC, MV** и даже применяем их с переменным успехом, стараясь нащупать эффективный подход к построению приложений. Но это лишь приводит к разнообразию реализаций наших приложений и частей функционала.
И поэтому я уже долгое время пытаюсь понять по каким правилам должно строиться фронтенд приложение чтобы оно удовлетворяло следующим критериям:
легкое расширение функционала приложения;
безболезненное внесение изменений в существующий функционал;
унифицированная структура приложения;
быстрый onboarding новых разработчиков на проект;
понятный и прозрачный код;
всегда понятно где в структуре файлов расположить ту или иную функциональность.
Какие у нас есть варианты?
«Организация файловой структуры это все что нам нужно»
Каждый лид или сеньор сами для себя выбирают варианты компоновки структуры приложения и выделения сущностей приложения. По итогу каждая система становится уникальной и неповторимой. И для того, чтобы разобраться в ней, нужны время и усилия, которые нужно будет тратить каждый раз при смене проекта. Плюс никто не отменял “бас фактор”.
Существует большое кол-во статей, описывающих «оптимальные», по мению авторов, варианты таких подходов. Пример.
Но это, в основном, про структуру файлов и частные случаи использования какого-то функционала. Такой подход только частично унифицирует структуру приложения, но этого мало для того, чтобы называться архитектурой. Может есть что-то лучше?
Domain Driven Design
Много умных дядек, таких как Мартин Фаулер и дядюшка Боб, написали много статей про него. На бэкенде в больших и сложных проектах он неплохо себя зарекомендовал. Но есть и много изъянов: туча абстракций, для простых действий нужно писать много кода, ну и разобраться, как готовить DDD та еще задача.
Есть примеры как готовить это на фронте, но, как видно, проблемы никуда не уходят и кол-во абстракций удручают. Простой onboarding тут невозможен, без прочтения “The Big Blue Book” и пары недель общения с ментором.
Есть переосмысленные подходы к архитектуре, которые больше похожи на правду и наверняка могут где-то успешно применены.
Основательная статья от Кхалила Стеммлера о возможной архитектуре клиентских приложений частично полагается на DDD подход, но при этом сильно его упрощает, освобождая нас от ненужных абстракций и смещая понятия в сторону фронт приложений.
Но бизнес логика в таких приложениях немного размывается и подход больше сфокусирован на функциональных слоях приложения, что отдаляет нас от требования к прозрачному коду и явной бизнес логики.
Джимми Богарт в своей статье пишет что DDD подход не совершенен и избыточен, и, как следствие, он предлагает переработанный подход vertical slices. И это отличный подход, о котором стоит почитать отдельно. Эта идея довольно простая и мы можем адаптировать ее к фронтенд приложениям.
Если DDD не удалось применить для наших нужд, то можно попробовать построить его на более общих правилах, которые предоставляет нам “Clear architecture”, ведь DDD основывается именно на них.
Clear architecture
Также есть попытки следовать всем постулатам чистой архитектуры и абстрагироваться от представления совсем. В этом случае мы сможем подменять view на любой фреймворк или вообще отказаться от его использования. Интересный подход и в некоторых случаях вполне обоснован и может оказаться отличным решением. Самый частый кейс, это использование одной и той же логики в браузере и на мобильном приложении.
Подробнее об этом можно почитать тут.
Разработчики Flutter тоже столкнулись с проблемой сложности переиспользования логики между различными представлениями, и предложили подход – Business Logic Component (BLoC). Он позволяет снизить нагрузку на компоненты пользовательского интерфейса, отделив от них бизнес-логику.
Тут пример одной из реализаций BLoC в React.
Вроде неплохо, но все же есть много вопросов. И почти нет сообщества, которое бы могло помочь с возникающими вопросами.
FCD – Feature Sliced Design
И недавно для меня стало открытием методология FCD – Feature Sliced Design. На мой взгляд лучшем решением будет обратить внимание именно на эту методологию.
Ссылка на офф сайт.
Методология не привязана к конкретному стеку технологий и применима к большинству frontend-приложений. Документация содержит примеры реализации на JavaScript + React, но FSD успешно адаптируется и к другим комбинациям инструментов.
Для проектирования архитектуры методология предлагает следующие архитектурные абстракциями на основе которых строиться наше приложение.
Ниже приведу описание терминов из документации:Layers
Первый уровень абстрагирования – согласно скоупу влияния.
app – инициализация приложения (init, styles, providers, …);
processes – бизнес-процессы приложения управляющие страницами (payment, auth, …);
pages – страницы приложения (user-page, …);
features – части функциональности приложения (auth-by-oauth, …);
entities – бизнес-сущности (viewer, order, …);
shared – переиспользуемый инфраструктурный код (UIKit, libs, API, …).
Slices
Второй уровень абстрагирования – согласно бизнес-домену.
Правила, по которым код разделяется на слайсы, зависят от конкретного проекта и его бизнес-правил и не определяются методологией
Segments
Третий уровень абстрагирования – согласно назначению в реализации.
ui – UI-представление модуля (components, widgets, canvas, …);
model – бизнес-логика модуля (store, effects/actions, hooks/contracts, …);
lib – вспомогательные библиотеки;
api – логика взаимодействия с API;
config – модуль конфигурации приложения и его окружения.
Ниже приведу пример описания фичи авторизации.
# Сегменты могут быть как файлами, так и директориями
|
├── features/auth # Layer: Бизнес-фичи
| | # Slice Group: Структурная группа “Авторизация пользователя”
| ├── by-phone/ # Slice: Фича “Авторизация по телефону”
| | ├── ui/ # Segment: UI-логика (компоненты)
| | ├── lib/ # Segment: Инфраструктурная-логика (helpers/utils)
| | ├── model/ # Segment: Бизнес-логика
| | └── index.ts #
| |
| ├── by-oauth/ # Slice: Фича “Авторизация по внешнему ресурсу”
| …
Помимо унификации структуры, мы получаем наглядную бизнес логику, отличное описание слоев приложения с примерами на популярных ЯП. Также есть ответы на вопросы о расположении функционала и понятные правила уменьшения зависимостей в коде.
Эта методология только развивается и есть хорошее комьюнити, которое так же как и мы задается вопросами архитектуры фронтенда.
Заключение
У каждого из подходов есть свои плюсы и минусы. Учитывая что каждый проект имеет разный размер, сложность и специфику и цели, то что подойдет многим не факт что подойдет вам. Надеюсь что после прочтения статьи вы откроете для себя что то новое и сможете улучшить ваши собственные проекты.
Также если вам интересно в своем Telegram я время от времени выкладываю интересные находки по фронтенду. И всем чистой архитектуры.
Не отписался — без памяти остался / Хабр | Веб-студия Nat.od.ua
Не отписался — без памяти остался / Хабр
Речь пойдёт про мощный инструмент, реализующий собой подход реактивного программирования и в разы упрощающий разработку — RxJS. В частности разберём один момент, про который нужно не забывать при использовании этой библиотеки, а именно — отписки.
Да, да, этот базовый момент может упускаться разработчиком, и это в свою очередь может привести к утечке памяти — об этом далее.
Кейс — отправка запросов на бэк и показ данных в реальном времениЗадача
Представим, что мы разрабатываем приложение, которое в реальном времени показывает нам курсы валют. Клиент часто обращается к бэку либо через обычные запросы, либо через веб-сокеты, и нам нужно каждый ответ от бэка отображать на стороне клиента.
Решение
Будем использовать RxJS в связке с Angular и нашу функцию, которая будет отдавать нам рандомный курс.
Функция будет нам отдавать через определённый промежуток времени курс, который будет сгенерирован рандомайзером:
// fake-currency.function.ts
import { interval } from ‘rxjs’;
import { map } from ‘rxjs/operators’;
// Простой рандомайзер, который отдаёт случайное число в определённом диапазоне
function getRandomByLimits(min: number, max: number) {
return Math.round(Math.random() * (max – min) + min);
}
// Функция, отдающая фейковый курс
export function fakeCurrency(time: number) {
const minLimit = 65;
const maxLimit = 78;
return interval(time).pipe(map(() => getRandomByLimits(minLimit, maxLimit)));
}
Рандомные курсы валют у нас есть, теперь нам нужно реализовать компонент нашей страницы.
// my-component.component.ts
import { Component } from ‘@angular/core’;
import { fakeCurrency } from ‘../fake-currency.function’;
@Component({
selector: ‘my-component’,
templateUrl: ‘./my-component.component.html’,
styleUrls: ,
})
export class MemoryLeakComponent {
// свойство, которое мы будем использовать в шаблоне
num: number;
constructor() {
fakeCurrency(500).subscribe((num) => {
this.num = num;
});
}
}
Шаблон:
Random exchange rate: {{ num }}
Поздравляю!!! 🥳🥳🥳
У нас получилось вывести рандомный курс валюты на страницу!!!
Также поздравляю с получением первой утечки памяти!)
О какой утечке памяти идёт речь?
Дело в том, что если мы с этой страницы переместимся на другую, то работа функции fakeCurrency не остановится. Мы можем это проверить добавив логирование в подписку.
// my-component.component.ts
import { Component } from ‘@angular/core’;
import { fakeCurrency } from ‘../fake-currency.function’;
@Component({
selector: ‘my-component’,
templateUrl: ‘./my-component.component.html’,
styleUrls: ,
})
export class MemoryLeakComponent {
// свойство, которое мы будем использовать в шаблоне
num: number;
constructor() {
// Случайный id
const id = Math.floor(Math.random() * 100000);
fakeCurrency(500).subscribe((num) => {
console.log(`ID: ${id}`, num);
this.num = num;
});
}
}
Как мы видим — отображения курса на странице нет, но в консоль продолжают сыпаться логи. Если юзер продолжительное время будет переключаться между страницами, то такая утечка может вызвать дикие тормоза на его компьютере.
Как же нам избежать этого?
Конечно же отписаться!)
И способов отписки существует несколько.
Async Pipe
В данном случае этот способ наиболее предпочтительный. Немножко изменим наш код:
// my-component.component.ts
import { Component } from ‘@angular/core’;
import { fakeCurrency } from ‘../fake-currency.function’;
import { Observable } from ‘rxjs’;
import { tap } from ‘rxjs/operators’;
@Component({
selector: ‘my-component’,
templateUrl: ‘./my-component.component.html’,
styleUrls: ,
})
export class MemoryLeakComponent {
// свойство, которое мы будем использовать в шаблоне
num$: Observable
constructor() {
// Случайный id
const id = Math.floor(Math.random() * 100000);
this.num$ = fakeCurrency(500)
.pipe(
tap((num) => console.log(`ID: ${id}`, num))
);
}
}
Не забываем про наш шаблон:
Random exchange rate: {{ num$ | async }}
Destroy Subject Pattern
Если же нам значение в шаблоне не нужно, но при этом подписаться всё-таки нужно, то можно воспользоваться паттерном Destroy Subject и оператором отписки takeUntil:
// my-component.component.ts
import { Component } from ‘@angular/core’;
import { fakeCurrency } from ‘../fake-currency.function’;
import { takeUntil } from ‘rxjs/operators’;
@Component({
selector: ‘my-component’,
templateUrl: ‘./my-component.component.html’,
styleUrls: ,
})
export class MemoryLeakComponent implements OnDestroy {
// свойство, которое мы будем использовать в шаблоне
num: number;
destroy$ = new Subject();
constructor() {
// Случайный id
const id = Math.floor(Math.random() * 100000);
fakeCurrency(500)
// Прокидываем оператор в поток и передаём ему другой поток
.pipe(takeUntil(this.destroy$))
.subscribe((num) => {
console.log(`ID: ${id}`, num);
this.num = num;
});
}
ngOnDestroy() {
// завершаем поток
// когда переданный поток завершается, то оператор takeUntil отписывается от текущего потока
this.destroy$.next();
this.destroy$.complete();
}
}Может есть решение красивее?
Конечно, есть!)
Не забываем, что Subject сущности — те же классы, от которых мы можем наследоваться. (Нагло беру пример из библиотеки taiga-ui)
Создаём сервис, который будет наследоваться от ReplaySubject и имплементировать OnDestroy интерфейс:
// destroy.service.ts
import { Injectable, OnDestroy } from ‘@angular/core’;
import { ReplaySubject } from ‘rxjs’;
@Injectable()
export class DestroyService extends ReplaySubject
constructor() {
super();
}
ngOnDestroy() {
this.next();
this.complete();
}
}
Именно в компонент нужно обязательно запровайдить DestroyService, с провайдингом в модуль такое не прокатит:
// my-component.component.ts
import { Component, Inject } from ‘@angular/core’;
import { fakeCurrency } from ‘../fake-currency.function’;
import { takeUntil } from ‘rxjs/operators’;
import { DestroyService } from ‘../destroy.service’;
@Component({
selector: ‘my-component’,
templateUrl: ‘./my-component.component.html’,
styleUrls: ,
// Провайдим сервис
providers:
})
export class MemoryLeakComponent implements OnDestroy {
// свойство, которое мы будем использовать в шаблоне
num: number;
constructor(@Inject(DestroyService) private destroy$: Observable
// Случайный id
const id = Math.floor(Math.random() * 100000);
fakeCurrency(500)
// Прокидываем оператор в поток и передаём ему параметром другой поток
.pipe(takeUntil(destroy$))
.subscribe((num) => {
console.log(`ID: ${id}`, num);
this.num = num;
});
}
}
Подытожим:
Почти во всех случаях нам нужно отписываться (исключение: потоки, которые завершаются самостоятельно);
По возможности больше использовать async pipe, если данные отображаются в шаблоне;
Если не обойтись без подписки в компоненте, то используйте операторы take*;
На этом всё, весь код из статьи можно найти тут.