Функционал частичного отображения текста с возможностью раскрытия полной версии — распространённый паттерн в веб-разработке. В этой статье мы разберём несколько способов реализации этого компонента с учётом производительности, доступности и SEO.
Основные сценарии использования
-
Сокращение длинных текстов на страницах с большим количеством контента
-
Улучшение пользовательского опыта за счёт компактного представления информации
-
Адаптация под мобильные устройства с ограниченным пространством экрана
-
Организация контента с акцентом на ключевой информации
SEO-рекомендация: Google допускает скрытие второстепенного контента, но основной материал должен быть виден без дополнительных действий.
Подготовка текста на серверной стороне
Очистка от HTML-разметки
<?php
// Удаляем HTML-теги и лишние пробелы
$cleanText = trim(strip_tags($str));
?>Безопасная обрезка текста
<?php
// Устанавливаем лимит символов
$previewLength = 180;
// Проверяем длину с учётом многобайтовых символов
if (mb_strlen($cleanText) > $previewLength) {
// Обрезаем до ближайшего пробела
$previewText = mb_substr($cleanText, 0, $previewLength);
$previewText = rtrim($previewText, "!,.-");
$lastSpacePos = mb_strrpos($previewText, ' ');
$previewText = $lastSpacePos
? mb_substr($previewText, 0, $lastSpacePos)
: $previewText;
$hiddenText = mb_substr($cleanText, mb_strlen($previewText));
} else {
$previewText = $cleanText;
$hiddenText = '';
}
?>HTML-разметка с учётом доступности
<div class="expandable-text">
<span class="text-preview"><?= htmlspecialchars($previewText) ?></span>
<?php if ($hiddenText): ?>
<span id="hidden-text" class="text-full" hidden aria-hidden="true">
<?= htmlspecialchars($hiddenText) ?>
</span>
<button
class="expand-button"
aria-expanded="false"
aria-controls="hidden-text"
>
Подробнее
</button>
<?php endif; ?>
</div>Стилизация компонента
.expandable-text {
position: relative;
line-height: 1.5;
}
.text-preview::after {
content: "...";
}
.text-full {
display: inline;
}
.expand-button {
background: none;
border: none;
color: #3498db;
cursor: pointer;
font-weight: bold;
padding: 0;
margin-left: 4px;
}
.expand-button:hover {
text-decoration: underline;
}
/* Для плавного раскрытия */
.text-full {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
display: block;
}
.expandable-text.expanded .text-full {
max-height: 1000px;
}
.expandable-text.expanded .text-preview::after {
content: "";
}JavaScript реализация
Основной вариант на чистом JavaScript
document.querySelectorAll('.expand-button').forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
const container = button.closest('.expandable-text');
const isExpanded = button.getAttribute('aria-expanded') === 'true';
const hiddenText = document.getElementById(button.getAttribute('aria-controls'));
// Переключаем состояние
button.setAttribute('aria-expanded', !isExpanded);
button.textContent = isExpanded ? 'Подробнее' : 'Скрыть';
hiddenText.hidden = isExpanded;
container.classList.toggle('expanded', !isExpanded);
// Для скринридеров
if (!isExpanded) {
hiddenText.setAttribute('aria-hidden', 'false');
} else {
setTimeout(() => {
hiddenText.setAttribute('aria-hidden', 'true');
}, 300); // После завершения анимации
}
});
});Оптимизация с Intersection Observer
const lazyLoadHiddenText = (button) => {
const hiddenTextId = button.getAttribute('aria-controls');
const hiddenText = document.getElementById(hiddenTextId);
if (hiddenText.dataset.loaded) return;
// Здесь может быть AJAX-загрузка контента
hiddenText.dataset.loaded = true;
};
document.querySelectorAll('.expand-button').forEach(button => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
lazyLoadHiddenText(entry.target);
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
observer.observe(button);
});CSS-only решение
<input type="checkbox" id="expand-toggle" class="expand-toggle" hidden>
<div class="expandable-text">
<span class="text-preview">Lorem ipsum dolor sit amet...</span>
<span class="text-full">...consectetur adipiscing elit.</span>
<label for="expand-toggle" class="expand-button"></label>
</div>.expand-toggle {
position: absolute;
opacity: 0;
}
.expand-toggle:not(:checked) ~ .text-full {
max-height: 0;
overflow: hidden;
display: block;
}
.expand-toggle:checked ~ .text-full {
max-height: 1000px;
}
.expand-toggle:not(:checked) ~ .expand-button::after {
content: 'Подробнее';
}
.expand-toggle:checked ~ .expand-button::after {
content: 'Скрыть';
}
.expand-button {
color: #3498db;
cursor: pointer;
font-weight: bold;
}
.expand-button:hover {
text-decoration: underline;
}Оптимизация производительности
-
Используйте CSS-анимации вместо JavaScript для плавного раскрытия
-
Применяйте ленивую загрузку для очень длинных текстов
-
Избегайте рефловов при изменении размера контента
-
Оптимизируйте перерисовку с помощью will-change
.text-full {
will-change: max-height;
}SEO и микроразметка
Добавьте структурированные данные для скрытого контента:
<div class="expandable-text" itemscope itemtype="https://schema.org/Question">
<span itemprop="text"><?= htmlspecialchars($previewText) ?></span>
<div itemprop="suggestedAnswer" itemscope itemtype="https://schema.org/Answer">
<span itemprop="text" id="hidden-text" class="text-full" hidden>
<?= htmlspecialchars($hiddenText) ?>
</span>
</div>
</div>Заключение
Мы рассмотрели несколько подходов к реализации компонента раскрывающегося текста:
-
Серверная генерация с PHP
-
Доступный JavaScript вариант с анимацией
-
Чистое CSS решение без JavaScript
Для большинства проектов рекомендуется использовать JavaScript-реализацию с учётом доступности. CSS-вариант подходит для простых случаев, когда не требуется сложная логика.
Ключевые рекомендации:
-
Всегда учитывайте доступность (ARIA-атрибуты)
-
Оптимизируйте производительность при работе с длинными текстами
-
Сохраняйте семантическую структуру контента
-
Тестируйте на различных устройствах и браузерах
Выбор конкретной реализации зависит от требований проекта и целевой аудитории. Для максимальной совместимости и доступности рекомендуем основной JavaScript-вариант с прогрессивным улучшением.