Назад

Реализация скрытия текста с возможностью раскрытия по клику

Главная
Блог
Реализация скрытия текста с возможностью раскрытия по клику

Функционал частичного отображения текста с возможностью раскрытия полной версии — распространённый паттерн в веб-разработке. В этой статье мы разберём несколько способов реализации этого компонента с учётом производительности, доступности и SEO.

Основные сценарии использования

  1. Сокращение длинных текстов на страницах с большим количеством контента

  2. Улучшение пользовательского опыта за счёт компактного представления информации

  3. Адаптация под мобильные устройства с ограниченным пространством экрана

  4. Организация контента с акцентом на ключевой информации

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;
}

Оптимизация производительности

  1. Используйте CSS-анимации вместо JavaScript для плавного раскрытия

  2. Применяйте ленивую загрузку для очень длинных текстов

  3. Избегайте рефловов при изменении размера контента

  4. Оптимизируйте перерисовку с помощью 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>

Заключение

Мы рассмотрели несколько подходов к реализации компонента раскрывающегося текста:

  1. Серверная генерация с PHP

  2. Доступный JavaScript вариант с анимацией

  3. Чистое CSS решение без JavaScript

Для большинства проектов рекомендуется использовать JavaScript-реализацию с учётом доступности. CSS-вариант подходит для простых случаев, когда не требуется сложная логика.

Ключевые рекомендации:

  • Всегда учитывайте доступность (ARIA-атрибуты)

  • Оптимизируйте производительность при работе с длинными текстами

  • Сохраняйте семантическую структуру контента

  • Тестируйте на различных устройствах и браузерах

Выбор конкретной реализации зависит от требований проекта и целевой аудитории. Для максимальной совместимости и доступности рекомендуем основной JavaScript-вариант с прогрессивным улучшением.

Нужен надежный исполнитель?
Разрабатываем сайты, выполняем миграцию на Битрикс, дорабатываем функционал, сопровождаем проекты, а также занимаемся поисковым продвижением и комплексным маркетингом
Получить консультацию
Читайте по теме
Все статьи
Нужен надежный исполнитель?
Разрабатываем сайты, выполняем миграцию на Битрикс, дорабатываем функционал, сопровождаем проекты, а также занимаемся поисковым продвижением и комплексным маркетингом
Получить консультацию
Все статьи