При верстке таблиц для блога или документации часто возникает проблема: как отобразить большое количество колонок на узких областях экрана. Один из практичных подходов — оставить таблицу в её естественном виде, но добавить для неё горизонтальную прокрутку с удобным управлением.
Представленное решение — это контейнер для таблицы, который автоматически определяет, нужно ли добавить возможность прокрутки. Если ширина таблицы превышает ширину родительского блока, появляется индикатор (иконка с анимацией), подсказывающий пользователю о возможности сдвинуть таблицу в сторону. Прокручивать таблицу можно, зажав её левой кнопкой мыши и перетащив (drag). После резкого движения скролл продолжается по инерции.
Это сохраняет структуру данных читаемой и не требует кардинального изменения макета таблицы (например, превращения в аккордеон или стопку карточек) на малых экранах.
Пример:
| Колонка 1 | Колонка 2 | Колонка 3 |
|---|---|---|
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | Lorem ipsum dolor sit amet |
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
| Колонка 1 | Колонка 2 | Колонка 3 |
|---|---|---|
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | Lorem ipsum dolor sit amet |
| Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
HTML:
Для обеспечения читаемости таблицы на всех устройствах рекомендуется задавать минимальную ширину для важных колонок прямо в HTML-разметке. Это можно сделать через атрибут style, например: <th style="min-width: 200px;">
Такой подход гарантирует, что содержимое ячеек не будет сжиматься до нечитаемого состояния, сохраняя удобство восприятия данных даже при активной прокрутке.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<div class="table-scrollable"> <div class="table-drag-indicator"></div> <table class="table"> <thead> <tr> <th>Заголовок 1</th> <th style="min-width: 200px;">Заголовок 2</th> <th style="min-width: 200px;">Заголовок 3</th> </tr> </thead> <tbody> <tr> <td>Данные 1</td> <td>Данные 2</td> <td>Данные 3</td> </tr> <!-- ... больше строк ... --> </tbody> </table> </div> |
CSS:
Стили изолированы для таблиц внутри класса .table-scrollable, что предотвращает конфликты.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
.table-scrollable .table { margin: 0; border: 2px solid #eaecef; border-spacing: 0; border-collapse: collapse; font-size: 16px } @media (max-width:767px) { .table-scrollable .table { font-size: 15px; } } .table-scrollable .table thead { background-color: #f8f9fa; color: #212529; } .table-scrollable .table th { padding: 10px 12px; font-weight: 600; text-align: left; border-bottom: 3px solid #337AB7; border-right: 2px solid #dee2e6; white-space: nowrap; background: rgba(191, 226, 255, 0.2); } .table-scrollable .table th:last-child { border-right: none; } .table-scrollable .table td { padding: 10px 12px; border-bottom: 2px solid #dee2e6; border-right: 2px solid #dee2e6; vertical-align: top; } .table-scrollable .table td:last-child { border-right: none; } .table-scrollable .table tbody tr:last-child td { border-bottom: none; } .table-scrollable .table tbody tr:nth-child(even) { background-color: #fcfcfc; } .table-scrollable .table tbody tr:hover { background-color: #f8f9fa; transition: background-color 0.15s ease-in-out; } .table-scrollable { overflow-x: auto; -webkit-overflow-scrolling: touch; position: relative; margin: 20px 0; } .table-scrollable::-webkit-scrollbar { height: 20px; } .table-scrollable::-webkit-scrollbar-track { background: transparent; border-radius: 10px; } .table-scrollable::-webkit-scrollbar-thumb { background-color: #77AEDB; border-radius: 10px; border-top: 4px solid #fff; border-bottom: 4px solid #fff; } .table-scrollable::-webkit-scrollbar-thumb:hover { background-color: #337AB7; } .table-scrollable.scrollable:not(.dragging) { cursor: grab; } .table-scrollable.scrollable.dragging { cursor: grabbing; user-select: none; } .table-drag-indicator { position: absolute; top: 20px; right: 20px; pointer-events: none; opacity: 0; width: 60px; height: 60px; box-shadow: 0 10px 22px rgba(0, 0, 0, 0.05), 0 5px 12px rgba(0, 0, 0, 0.1); background-color: rgba(255, 255, 255, 0.85); backdrop-filter: blur(6px); background-size: 60%; background-position: center center; background-repeat: no-repeat; border-radius: 50%; border: 4px solid #fff; z-index: 20; background-image: url("data:image/svg+xml;charset=UTF-8, %3csvg xmlns='http://www.w3.org/2000/svg' fill='%23337AB7' viewBox='0 0 64 64'%3e%3cpath d='M58.9,29.6c-2.8-2.1-6.8-1.9-9.4,0.3c-1.1,0.9-2,2.1-3,3.2c-0.5,0.6-1,1.1-1.5,1.7c0-6.2,0-12.5,0-18.7 c0-1.8,0.1-3.5-1.1-5.1c-2.5-3.3-7.9-3.2-10.1,0.3c-1,1.5-0.8,3.2-0.8,4.9c0,1.8,0,3.6,0,5.4c-2-0.5-4.3,0-5.7,1.8 c-0.4,0.5-0.6,0.9-0.8,1.5c-0.6-0.2-1.1-0.3-1.7-0.3c-2.4,0-4.2,1.4-4.9,3.4c-2-0.6-4.3-0.2-5.8,1.5c-1.6,1.8-1.2,4.3-1.2,6.5c0,1.2,0,2.4,0,3.7c0,4.1-0.6,8.8,1.7,12.5c1,1.6,1.9,3.2,3.1,4.6c2.1,2.4,5.2,3.6,8.3,3.7c1.8,0,3.6,0,5.4,0c2,0,4,0.1,5.9-0.4 c4-1.2,6.9-4.7,9.5-7.8c4.3-5.1,8.6-10.2,12.8-15.3C61.5,34.5,61.4,31.5,58.9,29.6z M57,33.9c0,0.1,0,0.3-0.2,0.4 c-2.8,3.2-5.7,6.5-8.5,9.8c-0.7,0.8-1.4,1.7-2.1,2.5c-2,2.4-4,5.1-6.4,7.2c-1.7,1.5-3.4,2.6-5.8,2.7c-1.4,0-2.7,0-4.1,0 c-3.6,0-7.6,0.5-9.9-2.9c-0.8-1.2-1.6-2.3-2.3-3.5c-1-1.6-1.1-3.2-1.1-4.9c0-1.9,0-3.9,0-5.8c0-2-0.2-4.1-0.1-6.1 c0-0.3,0.1-0.7,0.3-1c0.4-0.6,1.2-0.7,1.7-0.2c0.5,0.4,0.6,1.1,0.6,1.7c0,0.9,0,1.7,0,2.6c0,2.6,4,2.6,4,0c0-1.8,0-3.5,0-5.3 c0-1,0.1-2.3,1.4-2.3c1,0,1.3,0.6,1.4,1.3c0,0.6,0,1.2,0,1.9c0,1.5,0,3,0,4.5c0,2.6,4,2.6,4,0c0-1.6,0.1-3.2,0.1-4.8 c0-1.6-0.1-3.2,0-4.7c0.1-1.6,2.6-1.8,2.9-0.3c0,3.3,0,6.5,0.1,9.8c0,2.6,4,2.6,4,0c0-1.7,0-3.5,0-5.2c0-4.4-0.1-8.7-0.1-13.1 c0-1.1,0-2.2,0-3.3c0-1.9,2.4-2.7,3.7-1.3c0.6,0.7,0.4,2.2,0.4,3c0,4.6,0,9.2,0,13.9c0,2.2,0,4.4,0,6.5c0,1.1,0,2.2,0,3.3 c0,1.3,1.2,2.3,2.5,2.1c0.8-0.2,1.3-0.9,1.9-1.5c1.7-1.9,3.4-3.9,5.1-5.8c0.8-0.9,1.6-2,2.9-2.4c0.7-0.2,1.5-0.2,2.2,0 C56,32.8,56.9,33.3,57,33.9z'/%3e%3cpath d='M20.2,18.8c0.7-0.8,0.8-2,0-2.8c-0.7-0.7-1.4-1.4-2.1-2.1c3,0,6,0,9,0c0.7,0,1.3,0,2,0c2.6,0,2.6-4,0-4 c-3.6,0-7.3,0-10.9,0c0.7-0.7,1.4-1.4,2.1-2.1C21,7,21,5.7,20.2,4.9c-0.9-0.9-2.1-0.6-3,0.2c-1.4,1.3-2.7,2.7-4,4 c-0.9,0.9-2.4,2-1.8,3.5c0.6,1.3,1.8,2.2,2.8,3.1c1.1,1,2.1,2.1,3.2,3.1C18.1,19.5,19.5,19.6,20.2,18.8z'/%3e%3c/svg%3e"); animation: drag-indicator 3s infinite; transition: opacity 0.4s; } @keyframes drag-indicator { 0%, 50%, 100% { transform: translateX(0); } 25%, 75% { transform: translateX(-12px); } } .table-scrollable.hide-indicator .table-drag-indicator { opacity: 0 !important; } .table-scrollable.scrollable .table-drag-indicator { opacity: 0.8; } |
JS:
Скрипт добавляет события для мыши (mousedown, mousemove, mouseup) и касаний (touchstart). При быстром "броске" срабатывает инерционный скролл.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
function initTableScroll() { $('.table-scrollable').each(function() { var $tableWrapper = $(this); // Проверяем, нужен ли скролл вообще function checkIfScrollable() { // Если элемент не виден - пропускаем if (!$tableWrapper.is(':visible')) { $tableWrapper.removeClass('scrollable'); return false; } var isScrollable = $tableWrapper[0].scrollWidth > $tableWrapper[0].clientWidth; $tableWrapper.toggleClass('scrollable', isScrollable); return isScrollable; } var isDragging = false; var startX, scrollLeft; var velocity = 0; var lastX = 0; var animationId; var isHorizontalScroll = false; // Универсальное получение координаты X function getPageX(e) { if (e.pageX !== undefined) return e.pageX; if (e.originalEvent && e.originalEvent.touches) { return e.originalEvent.touches[0].pageX; } return 0; } // Drag-скролл мышью function startDrag(e) { $tableWrapper.addClass('hide-indicator'); if (!checkIfScrollable()) return; isDragging = true; $tableWrapper.addClass('dragging'); startX = getPageX(e); scrollLeft = $tableWrapper.scrollLeft(); lastX = startX; velocity = 0; isHorizontalScroll = false; } function doDrag(e) { if (!isDragging) return; var x = getPageX(e); if (!x) return; var deltaX = startX - x; if (!isHorizontalScroll) { var deltaY; if (e.movementY !== undefined) { deltaY = Math.abs(e.movementY); } else if (e.originalEvent && e.originalEvent.touches) { var touch = e.originalEvent.touches[0]; deltaY = Math.abs(touch.pageY - startX); } else { deltaY = 0; } if (Math.abs(deltaX) > Math.abs(deltaY) * 1.5) { isHorizontalScroll = true; e.preventDefault(); } } if (isHorizontalScroll) { var walk = deltaX * 1.5; $tableWrapper.scrollLeft(scrollLeft + walk); velocity = (x - lastX) * 16; lastX = x; } } function endDrag() { if (!isDragging) return; isDragging = false; $tableWrapper.removeClass('dragging'); if (isHorizontalScroll && Math.abs(velocity) > 1) { cancelAnimationFrame(animationId); var inertiaScroll = function() { velocity *= 0.92; if (Math.abs(velocity) > 0.5) { $tableWrapper.scrollLeft($tableWrapper.scrollLeft() - velocity); animationId = requestAnimationFrame(inertiaScroll); } else { cancelAnimationFrame(animationId); } }; animationId = requestAnimationFrame(inertiaScroll); } } // События для мыши $tableWrapper.on('mousedown', startDrag); $(document).on('mousemove', doDrag); $(document).on('mouseup', function() { endDrag(); $(document).off('mousemove', doDrag); }); $tableWrapper.on('mousedown', function() { $(document).on('mousemove', doDrag); }); // События для touch-устройств $tableWrapper.on('touchstart', function(e) { $tableWrapper.addClass('hide-indicator'); }); // Отмена контекстного меню при drag $tableWrapper.on('contextmenu', function(e) { if (isDragging) { e.preventDefault(); return false; } }); // Инициализация setTimeout(function() { checkIfScrollable(); }, 100); // Обновляем при изменении размера окна $(window).on('resize', function() { checkIfScrollable(); }); // Отменяем drag если клик был на ссылке или кнопке внутри таблицы $tableWrapper.on('mousedown touchstart', 'a, button, .clickable', function(e) { e.stopPropagation(); }); }); } initTableScroll(); // Дополнительно при ресайзе $(window).on('resize', function() { setTimeout(initTableScroll, 100); }); |

Добавить комментарий: