[Перевод] Выделение и CSS
Навык выделения текста и других объектов сформировался у пользователей компьютеров много лет назад. Мы выделяем содержимое веб-страниц по разным причинам. Возможно, нужно скопировать текст и где-то его процитировать, возможно — кому-то просто легче читать текст, выделяя его фрагменты. На мобильных устройствах, правда, выделять что-либо сложнее. Меня, например, это раздражает. Мне не нравится выделять содержимое веб-страниц на телефоне. Эта операция кажется какой-то «неправильной».
В этом материале я расскажу обо всём, что нужно знать о стилизации выделений средствами CSS. В частности, речь пойдёт о псевдоэлементе ::selection
и о свойстве user-select
. Эта статья направлена на то, чтобы показать всем желающим возможности CSS по работе с выделениями, и на то, чтобы рассказать о том, когда и как использовать разные методы работы с выделениями.
Основы
На MDN можно узнать о том, что псевдоэлемент ::selection
позволяет применить стили к части документа, который был выделен пользователем (например, с помощью мыши).
Для использования ::selection
достаточно воспользоваться следующей конструкцией:
p::selection {
color: #fff;
background-color: #000;
}
Выделенный текст
Вот пример, с которым можно поэкспериментировать.
Свойства, поддерживаемые: selection
Стоить отметить, что псевдоэлемент ::selection
поддерживает только свойства color
, background
и text-shadow
.
Настройка собственных эффектов выделения
Что если нам нужно, чтобы выделение выглядело бы по-особенному? Например, чтобы выделение имело бы определённую высоту или некий интересный фон? Взгляните на следующий рисунок.
Пример особой настройки выделения
Это возможно, хотя и потребует приложения некоторых усилий. Вот как сделано выделение, показанное выше:
- Добавлен псевдоэлемент, с тем же текстом, который мы выделяем. Затем псевдоэлементу задано свойство
height: 50%
и белый фоновый цвет. - Псевдоэлемент расположен над исходным текстом.
Если теперь выделить текст, то псевдоэлемент перекроет 50% текста по вертикали. Это позволяет сымитировать нужный нам эффект.
p {
position: relative;
color: #fff;
}
p:after {
content: attr(data-content);
position: absolute;
color: #000;
top: 0;
left: 0;
width: 100%;
height: 50%;
background-color: #fff;
}
p::selection {
background: rgba(76, 125, 225, 0.18);
}
Об этой методике я узнал здесь.
Ещё один вариант подобного выделения представлен ниже. Здесь я, вместо сплошного выделения, реализовал выделение в виде CSS-градиента. Смысл тут заключается в использовании белого градиента с высотой в 50% и в однократном заполнении элемента фоновым рисунком благодаря использованию значения no-repeat
при настройке свойства background
.
h1:after {
content: attr(data-content);
position: absolute;
color: #000;
top: 0;
left: 0;
width: 100%;
background: linear-gradient(#fff, #fff) top/100% 50% no-repeat;
}
На следующем рисунке показано разъяснение этой методики.
Реализация градиентного выделения
Надеюсь, я смог понятно объяснить эту идею. Вот рабочий пример.
Анимирование выделения
Работая над предыдущим примером, я задался следующим вопросом: «Реально ли анимировать выделение?». Например, в процессе выделения текста высота выделения составляет 50%. А когда указатель мыши уводят в сторону, высота выделения увеличивается до 80%. Как это сделать? А вот так:
p {
transition: background 0.3s ease-out;
}
p:hover:after {
background-size: 100% 80%;
}
Текст в процессе выделения
Текст после завершения выделения
Вот видео, в котором демонстрируется анимированное выделение.
Многострочный текст
Представленная выше методика настройки выделения, к сожалению, не подходит для многострочного текста. Для того чтобы, всё же, реализовать нечто подобное и для такого текста, нужно прибегнуть к возможностям JavaScript и поместить каждое слово во встроенный (строчный) элемент, например — в . После того, как каждое слово окажется в собственном элементе
, к каждому из таких элементов надо добавить псевдоэлемент. А уже после этого к многострочному тексту можно применить вышеописанный эффект.
Вот скрипт, позволяющий поместить каждое слово в -контейнер:
let paragraph = document.querySelector(".text");
const words = paragraph.textContent.split(" ");
paragraph.innerHTML = "";
words.forEach(function (word) {
let wordItem = document.createElement("span");
wordItem.setAttribute("data-word", word);
wordItem.innerHTML = word + " ";
paragraph.appendChild(wordItem);
});
После этого элементы надо стилизовать. Затем к каждому из них надо добавить псевдоэлемент:
span {
position: relative;
font-size: 1.25rem;
line-height: 1.4;
}
span::after {
content: attr(data-word);
position: absolute;
left: 0;
right: 0;
top: -0.28em;
height: 75%;
padding-top: 0.14em;
background: #fff;
pointer-events: none;
}
span::selection {
background: rgba(#4C7DE1, 0.18);
}
Если посмотреть на эту конструкцию в деле, то окажется, что она работает, но не совсем так, как можно ожидать. Ниже показан пример выделения многострочного текста. Можно заметить, что выделение выглядит неоднородным.
Неоднородное выделение
Я бы сказал, что подобное многострочное выделение получается не очень хорошим, и что его не стоит использовать в глобальном масштабе. Возможно, его стоит применять лишь, скажем, для организации выделения какого-то отдельного абзаца.
Тут с таким выделением можно поэкспериментировать.
Креативный подход к использованию :: selection и text-shadow
Так как одним из свойств, которые поддерживает псевдоэлемент ::selection
, является text-shadow
, мы можем попытаться достичь каких-нибудь интересных эффектов, используя несколько теней текста. Исследуем возможности, которые открывает перед нами эта идея.
▍Выделение с длинными тенями
Выделенный текст отбрасывает длинные тени
Вот как реализовать этот эффект:
p::selection {
color: #444444;
background: #ffffff;
text-shadow: 1px 0px 1px #cccccc, 0px 1px 1px #eeeeee, 2px 1px 1px #cccccc, 1px 2px 1px #eeeeee, 3px 2px 1px #cccccc, 2px 3px 1px #eeeeee, 4px 3px 1px #cccccc, 3px 4px 1px #eeeeee, 5px 4px 1px #cccccc, 4px 5px 1px #eeeeee, 6px 5px 1px #cccccc, 5px 6px 1px #eeeeee, 7px 6px 1px #cccccc;
}
▍Эффект контурного текста
Выделенный текст становится контурным
Эту идею я нашёл в данной статье. Речь идёт о том, что с помощью свойства text-shadow
можно сымитировать эффект контурного текста.
p::selection {
color: #fff;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
▍Эффект размытия
Выделенный текст выглядит размытым
Ещё один интересный эффект, который можно применить к выделенному тексту, заключается в размытии этого текста. Суть тут в том, чтобы использовать при настройке цвета текста свойство color: transparent
. Тени, задаваемые с помощью text-shadow
, при этом никуда не исчезнут, что и даст нужный эффект.
p::selection {
color: transparent;
text-shadow: 0 0 3px #fff;
}
Уверен, что вы сами сможете придумать ещё очень много примеров применения text-shadow
для стилизации выделений. Это свойство даёт нам безграничные возможности.
▍Тени текстов и производительность
Не рекомендуется использовать слишком сложные стили при настройке text-shadow
. Дело в том, что чрезмерное увлечение этим свойством приводит к проблемам с производительностью. Вот видео, демонстрирующее один из примеров таких проблем.
Использование очень сложных стилей при настройке выделения текста
Представленный здесь неоновый эффект очень сложен. Обратите внимание на то, что при выделении этого текста заметна задержка между моментом выделения текста и моментом применения стилизации. Кроме того, обратите внимание на то, что сверху и слева появляется то, что появляться не должно. Поэтому прошу вас использовать text-shadow
осмотрительно.
Выделяются ли элементы форм?
Краткий ответ на вопрос, вынесенный в заголовок этого раздела, будет звучать как «да». Мне кажется, что это неправильно: выделяешь страницу, а оказывается, что содержимое внутри полей ввода тоже выделяется. Вот как это выглядит.
Содержимое внутри полей ввода выделяется
Тут может быть выделен и текст внутри кнопки. В разделе, посвящённом user-select
, мы поговорим о том, стоит или нет позволять пользователям выделять формы элементов.
Вот пример.
Исследование свойства user-select
Свойство user-select
даёт нам возможность задавать возможность выделения конкретного текста пользователем. Это свойство может оказаться полезным для отключения возможности выделения текста, что может пригодиться для ограничения возможностей пользователя по выделению материалов, расположенных рядом друг с другом. Вот стандарт, описывающий user-select
.
Это свойство может принимать следующие значения: none, auto, text, contain, all
.
Сценарии использования user-select
▍Текст и иконка
Если в элементе есть текст и иконка — в виде символа или значка, взятого из какого-нибудь шрифта, то при выделении текста будет выделяться и эта иконка. Рассмотрим пример, представленный на следующем рисунке.
Кнопка с текстом и иконкой
Вот код этой кнопки:
При выделении этого элемента он выглядит так, как показано ниже.
Выделенная кнопка
В подобном совершенно нет необходимости. Обратите внимание на то, что в разметке используется атрибут aria-hidden
, скрывающий иконку от средств чтения с экрана. Для того чтобы решить проблему с выделение того, что выделять не нужно, мы можем воспользоваться следующим стилем:
button span[aria-hidden="true"] {
user-select: none;
}
Это позволяет запретить выделение иконки. И, в то же время, мы привязываем запрет выделения к атрибуту aria-hidden
. В результате всё, что не должно выделяться, скорее всего, не должно быть видимым и для средств чтения с экрана.
▍Флажки
Меня раздражает такое поведение флажков, когда, устанавливая или снимая флажок, я случайно выделяю текст его описания. Вот как это выглядит.
Текст описания флажка выделен случайно
Решить эту проблему можно, стилизовав элемент следующим образом:
label {
user-select: none;
}
▍Выделение всего текста
Значение all
, которое может принимать свойство user-select
, позволяет добиваться интересного эффекта. Если это свойство с таким значением есть у родительского элемента, то весь текст, содержащийся в таком элементе, можно выделить одним щелчком мыши. Это может оказаться полезным для работы с текстовым содержимым, которое должно выделяться целиком. Например — для выделения фрагментов кода, имеющихся на странице:
.embed-code {
user-select: all;
}
Фрагмент текста, оформленный таким стилем, можно выделить одним щелчком мыши по нему.
Веб-приложения
Веб-приложение должно восприниматься пользователем как настоящее приложение. Можно ли выделять текст кнопок в обычных приложениях? Нет, нельзя. Важно, чтобы веб-приложения отражали привычные черты обычных приложений, делая это даже с учётом того, что они созданы с использованием HTML и CSS.
Рассмотрим несколько примеров из жизни.
▍Slack
В Slack можно выделять метки и поля ввода. Однако тексты кнопок не выделяются.
Подписи кнопок не выделяются
Вот ещё один пример.
Подпись в заголовке модального окна выделяется
А дату чата выделить нельзя.
Дату выделить нельзя
В целом — мне кажется странным то, что в приложении можно выделять некоторые тексты, которые, вроде бы, не должны поддерживать выделение. В интерфейсе Slack есть места, где используется user-select: none
, но таких мест меньше, чем можно ожидать. Например мне, как пользователю, нет никакой выгоды от выделения заголовка модального окна.
▍Notion
Подход к выделению элементов, реализованный в Notion, мне нравится больше. Это веб-приложение больше похоже на реальное приложение, а не на веб-сайт, любую часть интерфейса которого можно выделить.
То, что не должно выделяться, не выделяется
Ни один фрагмент текста с этого рисунка не выделяется. Это — именно то, чего можно ожидать от приложения.
Не используйте глобальное отключение выделения
Не рекомендуется отключать выделение глобально. Когда вы пользуетесь отключением выделения — постарайтесь, чтобы оно отключалось бы лишь у элементов, для которых оно не имеет смысла. Для этого можно создать вспомогательный класс. Например — такой:
.disable-selection {
-webkit-user-select:text;
-moz-user-select:text;
-ms-user-select:text;
user-select:text;
}
Нехороший паттерн
Есть один UX-паттерн, который мне крайне не нравится. Он заключается в показе предупреждения при попытке выделения текста. Это раздражает и создаёт у пользователя такое ощущение, будто его взаимодействием с сайтом пытаются управлять. Пример этого паттерна показан ниже.
Запрет выделения с показом уведомления
Пожалуйста, не делайте так.
Выделение на мобильных устройствах
Существует свойство -webkit-touch-callout
для iOS Safari, которое должно отключать показ стандартной подсказки, выводимой при выделении текста. Я попытался воспользоваться этим свойством, но оно не работает.
p {
-webkit-touch-callout: none;
}
Стили ::selection
тоже не работают.
А свойство user-select: none
работает так, как ожидается.
Я постарался найти реальный пример, иллюстрирующий эту проблему. Я скопировал фрагмент текста из Википедии. При этом был скопирован и совершенно ненужный мне текст (listen)
. Это раздражает.
Вместе с полезным текстом скопировано и (listen)
Вместо того чтобы позволять пользователю копировать это вот «listen», лучше было бы добавить к этому элементу стиль user-select: none
. В результате при копировании текста, содержащего этот элемент, он копироваться не будет.
Итоги
Здесь мы рассмотрели методы настройки выделения элементов веб-страниц с использованием средств CSS. Возможно, вам интересно будет взглянуть и на этот материал.
Уважаемые читатели! Как вы настраиваете выделение текстов в своих проектах?