При работе с векторной графикой и интерактивными картами важную роль играет точность взаимодействия пользователя с объектами. Линии на карте часто имеют небольшую визуальную толщину, поэтому попадание курсором мыши или касанием пальца непосредственно в геометрию линии может быть затруднено. Для решения этой задачи применяется параметр tolerance.
В контексте линий tolerance определяет допустимую область вокруг геометрии линии, которая также считается частью объекта при выполнении различных операций поиска, выбора или обработки событий. Фактически создаётся невидимая зона вокруг линии, увеличивающая область взаимодействия.
Без использования tolerance пользователь должен попадать строго в контур линии. При наличии ненулевого значения tolerance взаимодействие становится значительно удобнее.
На практике линии могут представлять:
Во многих случаях ширина линии на экране составляет всего несколько пикселей. Попасть точно в такой объект бывает сложно даже с помощью мыши, не говоря о сенсорных устройствах.
Использование tolerance позволяет:
Предположим, имеется линия дороги.
Визуально она отображается шириной 2 пикселя.
-------------------------
Если tolerance равен 0, то попадание должно происходить непосредственно по линии.
Если tolerance увеличен до нескольких пикселей, вокруг линии создаётся дополнительная область:
=========================
-------------------------
=========================
Пользователь может взаимодействовать не только с самой линией, но и с окружающей зоной.
С точки зрения алгоритмов проверки попадания фактически происходит вычисление расстояния от точки взаимодействия до ближайшего сегмента линии.
Если расстояние меньше tolerance, объект считается найденным.
Одной из наиболее распространённых областей применения является метод поиска объектов на карте.
Пример получения объектов под курсором:
map.on('click', (e) => {
const features = map.queryRenderedFeatures(e.point);
console.log(features);
});
По умолчанию поиск выполняется по конкретной точке.
Однако можно расширить область поиска:
map.on('click', (e) => {
const bbox = [
[e.point.x - 5, e.point.y - 5],
[e.point.x + 5, e.point.y + 5]
];
const features = map.queryRenderedFeatures(bbox);
console.log(features);
});
В данном примере вокруг точки клика создаётся область 10×10 пикселей.
Такой подход фактически реализует механизм tolerance вручную.
Рассмотрим ситуацию, когда на карте отображаются маршруты.
map.addLayer({
id: 'routes',
type: 'line',
source: 'routes-source',
paint: {
'line-color': '#ff0000',
'line-width': 2
}
});
Попадание в линию толщиной 2 пикселя может быть затруднительным.
Поэтому часто используется дополнительный радиус поиска:
map.on('mousemove', (e) => {
const bbox = [
[e.point.x - 8, e.point.y - 8],
[e.point.x + 8, e.point.y + 8]
];
const features = map.queryRenderedFeatures(
bbox,
{
layers: ['routes']
}
);
if (features.length > 0) {
map.getCanvas().style.cursor = 'pointer';
}
});
В этом случае линия будет обнаруживаться даже тогда, когда курсор находится рядом с ней.
Начинающие разработчики нередко путают эти понятия.
Определяет визуальное отображение.
'line-width': 8
Линия действительно становится толще на экране.
Не изменяет внешний вид объекта.
Он влияет исключительно на область обнаружения или взаимодействия.
Например:
'line-width': 2
Визуально линия остаётся тонкой.
Но поиск может выполняться с радиусом:
10 пикселей
В результате пользователь воспринимает объект как более удобный для выбора.
Распространённая техника в Mapbox GL JS — создание дополнительного слоя с увеличенной толщиной.
Основная линия:
map.addLayer({
id: 'road-line',
type: 'line',
source: 'roads',
paint: {
'line-color': '#0066ff',
'line-width': 3
}
});
Слой для обработки событий:
map.addLayer({
id: 'road-hitbox',
type: 'line',
source: 'roads',
paint: {
'line-color': '#000000',
'line-width': 20,
'line-opacity': 0
}
});
Теперь события можно навешивать именно на слой hitbox:
map.on('click', 'road-hitbox', (e) => {
console.log('Линия выбрана');
});
Пользователь видит тонкую линию, но взаимодействует с гораздо большей областью.
Такой подход часто оказывается эффективнее прямого увеличения области поиска.
На мобильных устройствах проблема точного попадания становится особенно заметной.
Палец закрывает значительную часть экрана и обладает гораздо меньшей точностью по сравнению с курсором мыши.
Поэтому рекомендуются более высокие значения tolerance:
| Устройство | Рекомендуемый диапазон |
|---|---|
| Мышь | 3–8 px |
| Планшет | 8–15 px |
| Смартфон | 10–20 px |
Чем меньше отображаемые линии, тем больше обычно требуется зона допуска.
Пусть имеется линия:
A ------------------- B
Пользователь нажал в точке P.
Алгоритм выполняет следующие действия:
distance <= tolerance
Такой подход используется во многих геоинформационных системах и алгоритмах пространственного поиска.
Небольшие значения tolerance практически не влияют на быстродействие.
Однако чрезмерно большие зоны поиска могут привести к дополнительным вычислениям.
Например:
const bbox = [
[e.point.x - 50, e.point.y - 50],
[e.point.x + 50, e.point.y + 50]
];
Область поиска составляет уже 100×100 пикселей.
Если карта содержит тысячи линий, количество потенциальных совпадений возрастает.
Следствием могут стать:
Поэтому значение tolerance должно быть достаточно большим для удобства пользователя, но не чрезмерным.
Часто требуется выделять линию при наведении курсора.
map.on('mousemove', (e) => {
const bbox = [
[e.point.x - 6, e.point.y - 6],
[e.point.x + 6, e.point.y + 6]
];
const features = map.queryRenderedFeatures(
bbox,
{
layers: ['routes']
}
);
if (features.length > 0) {
map.setFeatureState(
{
source: 'routes',
id: features[0].id
},
{
hover: true
}
);
}
});
Даже при небольшом отклонении курсора от линии подсветка продолжает работать корректно.
Это создаёт ощущение плавного и отзывчивого интерфейса.
Выбор зависит от нескольких факторов:
При сильном уменьшении масштаба линии становятся визуально тоньше.
В таких случаях tolerance обычно увеличивают.
Если линии расположены близко друг к другу, слишком большой tolerance может приводить к ошибочному выбору соседних объектов.
Для мобильных устройств значения должны быть выше.
Чем тоньше линия, тем полезнее дополнительная область взаимодействия.
const radius = 1;
Пользователь вынужден попадать практически точно в линию.
const radius = 30;
Выбираются соседние объекты, расположенные далеко от курсора.
Настройки, удобные для мыши, часто оказываются неудобными для сенсорного ввода.
'line-width': 15
Увеличение ширины линии ради удобства выбора ухудшает внешний вид карты. Намного лучше разделять визуальное отображение и область взаимодействия.
Для большинства проектов с Mapbox GL JS хорошо работают следующие значения:
| Сценарий | Tolerance |
|---|---|
| Тонкие маршруты | 5–8 px |
| Дороги | 6–10 px |
| Пешеходные треки | 8–12 px |
| Мобильные карты | 10–20 px |
| Плотные инженерные сети | 3–6 px |
Наиболее универсальным решением считается сочетание двух подходов:
Такой подход обеспечивает высокую точность выбора объектов, удобство взаимодействия и стабильную производительность даже при работе с большими наборами линейных геоданных.