Горизонтальная полоса прокрутки сверху и снизу таблицы

У меня очень большой table на моей странице. Поэтому я решил поставить горизонтальную полосу прокрутки, которая находится в нижней части таблицы. Но я бы хотел, чтобы эта полоса прокрутки была также сверху на столе.

Что я имею в шаблоне это:

<div style="overflow:auto; width:100%; height:130%">
<table id="data" style="width:100%">

Это возможно сделать только в HTML и CSS?

23 ответа

Решение

Чтобы смоделировать вторую горизонтальную полосу прокрутки поверх элемента, поместите "пустышку" div над элементом, имеющим горизонтальную прокрутку, достаточно высоко для полосы прокрутки. Затем присоедините обработчики события "scroll" для фиктивного элемента и реального элемента, чтобы синхронизировать другой элемент при перемещении полосы прокрутки. Фиктивный элемент будет выглядеть как вторая горизонтальная полоса прокрутки над реальным элементом.

Для живого примера, см. Эту скрипку

Вот код:

HTML:

<div class="wrapper1">
  <div class="div1"></div>
</div>
<div class="wrapper2">
  <div class="div2">
    <!-- Content Here -->
  </div>
</div>

CSS:

.wrapper1, .wrapper2 {
  width: 300px;
  overflow-x: scroll;
  overflow-y:hidden;
}

.wrapper1 {height: 20px; }
.wrapper2 {height: 200px; }

.div1 {
  width:1000px;
  height: 20px;
}

.div2 {
  width:1000px;
  height: 200px;
  background-color: #88FF88;
  overflow: auto;
}

JS:

$(function(){
  $(".wrapper1").scroll(function(){
    $(".wrapper2").scrollLeft($(".wrapper1").scrollLeft());
  });
  $(".wrapper2").scroll(function(){
    $(".wrapper1").scrollLeft($(".wrapper2").scrollLeft());
  });
});

Есть один способ добиться этого, о котором я не видел, чтобы здесь кто-то упоминал.

При повороте родительского контейнера на 180 градусов и дочернего контейнера снова на 180 градусов полоса прокрутки будет показана вверху.

.parent {
  transform: rotateX(180deg);
  overflow-x: auto;
} 
.child {
  transform: rotateX(180deg);
}

Для справки см. Проблему в репозитории w3c.

Попробуйте использовать jquery.doubleScroll плагин

JQuery:

$(document).ready(function(){
  $('#double-scroll').doubleScroll();
});

CSS

#double-scroll{
  width: 400px;
}

HTML

<div id="double-scroll">
  <table id="very-wide-element">
    <tbody>
      <tr>
        <td></td>
      </tr>
    </tbody>
  </table>
</div>

Без JQuery (2017)

Поскольку вам может не понадобиться JQuery, вот рабочая версия Vanilla JS, основанная на ответе @StanleyH:

var wrapper1 = document.getElementById('wrapper1');
var wrapper2 = document.getElementById('wrapper2');
wrapper1.onscroll = function() {
  wrapper2.scrollLeft = wrapper1.scrollLeft;
};
wrapper2.onscroll = function() {
  wrapper1.scrollLeft = wrapper2.scrollLeft;
};
#wrapper1, #wrapper2{width: 300px; border: none 0px RED;
overflow-x: scroll; overflow-y:hidden;}
#wrapper1{height: 20px; }
#wrapper2{height: 100px; }
#div1 {width:1000px; height: 20px; }
#div2 {width:1000px; height: 100px; background-color: #88FF88;
overflow: auto;}
<div id="wrapper1">
    <div id="div1">
    </div>
</div>
<div id="wrapper2">
    <div id="div2">
    aaaa bbbb cccc dddd aaaa bbbb cccc 
    dddd aaaa bbbb cccc dddd aaaa bbbb 
    cccc dddd aaaa bbbb cccc dddd aaaa 
    bbbb cccc dddd aaaa bbbb cccc dddd
    </div>
</div>

Ответ StanleyH был превосходным, но у него была одна неприятная ошибка: нажатие на затененную область полосы прокрутки больше не переходит к выбранному вами выбору. Вместо этого вы получите очень маленький и несколько раздражающий прирост положения полосы прокрутки.

Протестировано: 4 версии Firefox (затронуты на 100%), 4 версии Chrome (затронуты на 50%).

Вот мой jsfiddle. Вы можете обойти это с таймаутом:

var timecount = 25;
var timer = null;
$(".wrapper1").scroll(function(){
    if(timer !== null) {
        clearTimeout(timer);
    }
    timer = setTimeout(function() {
        $(".wrapper2").scrollLeft($(".wrapper1").scrollLeft());
    }, timecount);
});

Поведение проблемы с принятым ответом:

Фактически желаемое поведение:

Так почему же это происходит? Если вы выполните код, вы увидите, что wrapper1 вызывает scrollLeft для wrapper2, а wrapper2 вызывает scrollLeft для wrapper1 и повторяет это бесконечно, поэтому у нас возникает проблема с бесконечным циклом. Или, скорее: продолжающаяся прокрутка пользователя вступает в конфликт с вызовом прокрутки от Wrappperx, и конечным результатом является отсутствие перехода по полосам прокрутки.

Надеюсь, это поможет кому-то еще!

Прежде всего, отличный ответ @StanleyH Если кому-то интересно, как сделать контейнер с двойной прокруткой с динамической шириной

CSS

.wrapper1, .wrapper2 { width: 100%; overflow-x: scroll; overflow-y: hidden; }
.wrapper1 { height: 20px; }
.div1 { height: 20px; }
.div2 { overflow: none; }

JS

$(function () {
    $('.wrapper1').on('scroll', function (e) {
        $('.wrapper2').scrollLeft($('.wrapper1').scrollLeft());
    }); 
    $('.wrapper2').on('scroll', function (e) {
        $('.wrapper1').scrollLeft($('.wrapper2').scrollLeft());
    });
});
$(window).on('load', function (e) {
    $('.div1').width($('table').width());
    $('.div2').width($('table').width());
});

HTML

<div class="wrapper1">
    <div class="div1"></div>
</div>
<div class="wrapper2">
    <div class="div2">
        <table>
            <tbody>
                <tr>
                    <td>table cell</td>
                    <td>table cell</td>
                    <!-- ... -->
                    <td>table cell</td>
                    <td>table cell</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

демонстрация

http://jsfiddle.net/simo/67xSL/

Решение только для javascript, основанное на ответах @HoldOffHunger и @bobince

<div id="doublescroll">

.

function DoubleScroll(element) {
            var scrollbar= document.createElement('div');
            scrollbar.appendChild(document.createElement('div'));
            scrollbar.style.overflow= 'auto';
            scrollbar.style.overflowY= 'hidden';
            scrollbar.firstChild.style.width= element.scrollWidth+'px';
            scrollbar.firstChild.style.paddingTop= '1px';
            scrollbar.firstChild.appendChild(document.createTextNode('\xA0'));
            var running = false;
            scrollbar.onscroll= function() {
                if(running) {
                    running = false;
                    return;
                }
                running = true;
                element.scrollLeft= scrollbar.scrollLeft;
            };
            element.onscroll= function() {
                if(running) {
                    running = false;
                    return;
                }
                running = true;
                scrollbar.scrollLeft= element.scrollLeft;
            };
            element.parentNode.insertBefore(scrollbar, element);
        }

    DoubleScroll(document.getElementById('doublescroll'));

Вы можете использовать плагин jquery, который сделает всю работу за вас:

Плагин будет обрабатывать всю логику для вас...

Код React+TypeScriptrap-2-h перенесен на TypeScript.

      import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { ReactNode } from 'react';
import { useRef } from 'react';

const useStyles = makeStyles((theme) => ({
  wrapper1: {
    width: '300px',
    height: '20px',
    overflowX: 'scroll',
    overflowY: 'hidden',
    border: 'none 0px black',
  },
  wrapper2: {
    width: '300px',
    height: '100px',
    overflowX: 'scroll',
    overflowY: 'hidden',
    border: 'none 0px black',
  },
  div1: {
    width: '1000px',
    height: '20px',
  },
  div2: {
    width: '1000px',
    height: '100px',
    backgroundColor: '#88FF88',
    overflow: 'auto',
  }
}));

export default function TopBottomScrollBars() {
  const classes = useStyles();
  const wrapRef1 = useRef<HTMLDivElement>(null);
  const wrapRef2 = useRef<HTMLDivElement>(null);

  const handleScroll: React.EventHandler<React.UIEvent<ReactNode>> = (event: React.UIEvent<React.ReactNode> ) => {
    const targetDiv: HTMLDivElement = event.target as HTMLDivElement;

    if(targetDiv === wrapRef1.current && wrapRef2.current) {
      wrapRef2.current.scrollLeft = targetDiv.scrollLeft;
    }
    else if(targetDiv === wrapRef2.current && wrapRef1.current) {
      wrapRef1.current.scrollLeft = targetDiv.scrollLeft;
    }
  };

  return (
    <div>
      <div ref={wrapRef1} className={classes.wrapper1} onScroll={handleScroll} >
        <div id="div1" className={classes.div1}>
      </div>
    </div>

    <div ref={wrapRef2} className={classes.wrapper2} onScroll={handleScroll}>
      <div id="div2" className={classes.div2}>
        aaaa bbbb cccc dddd aaaa bbbb cccc 
        dddd aaaa bbbb cccc dddd aaaa bbbb 
        cccc dddd aaaa bbbb cccc dddd aaaa 
        bbbb cccc dddd aaaa bbbb cccc dddd
      </div>
    </div>
    </div>
  );
}

На основе решения @StanleyH я создал директиву AngularJS, демо-версию для jsFiddle.

Легко использовать:

<div data-double-scroll-bar-horizontal> {{content}} or static content </div>

Не требуется jQuery.

Насколько я знаю, это невозможно с HTML и CSS.

Соединение скроллеров работало, но в том виде, в котором оно написано, оно создает цикл, который замедляет прокрутку в большинстве браузеров, если щелкнуть часть более легкой полосы прокрутки и удерживать ее (а не при перетаскивании скроллера).

Я исправил это с флагом:

$(function() {
    x = 1;
    $(".wrapper1").scroll(function() {
        if (x == 1) {
            x = 0;
            $(".wrapper2")
                .scrollLeft($(".wrapper1").scrollLeft());
        } else {
            x = 1;
        }
    });


    $(".wrapper2").scroll(function() {
        if (x == 1) {
            x = 0;
            $(".wrapper1")
                .scrollLeft($(".wrapper2").scrollLeft());
        } else {
            x = 1;
        }
    });
});

Вот пример для VueJS

index.page

<template>
  <div>
    <div ref="topScroll" class="top-scroll" @scroll.passive="handleScroll">
      <div
        :style="{
          width: `${contentWidth}px`,
          height: '12px'
        }"
      />
    </div>
    <div ref="content" class="content" @scroll.passive="handleScroll">
      <div
        :style="{
          width: `${contentWidth}px`
        }"
      >
        Lorem ipsum dolor sit amet, ipsum dictum vulputate molestie id magna,
        nunc laoreet maecenas, molestie ipsum donec lectus ut et sit, aut ut ut
        viverra vivamus mollis in, integer diam purus penatibus. Augue consequat
        quis phasellus non, congue tristique ac arcu cras ligula congue, elit
        hendrerit lectus faucibus arcu ligula a, id hendrerit dolor nec nec
        placerat. Vel ornare tincidunt tincidunt, erat amet mollis quisque, odio
        cursus gravida libero aliquam duis, dolor sed nulla dignissim praesent
        erat, voluptatem pede aliquam. Ut et tellus mi fermentum varius, feugiat
        nullam nunc ultrices, ullamcorper pede, nunc vestibulum, scelerisque
        nunc lectus integer. Nec id scelerisque vestibulum, elit sit, cursus
        neque varius. Fusce in, nunc donec, volutpat mauris wisi sem, non
        sapien. Pellentesque nisl, lectus eros hendrerit dui. In metus aptent
        consectetuer, sociosqu massa mus fermentum mauris dis, donec erat nunc
        orci.
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      contentWidth: 1000
    }
  },
  methods: {
    handleScroll(event) {
      if (event.target._prevClass === 'content') {
        this.$refs.topScroll.scrollLeft = this.$refs.content.scrollLeft
      } else {
        this.$refs.content.scrollLeft = this.$refs.topScroll.scrollLeft
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.top-scroll,
.content {
  overflow: auto;
  max-width: 100%;
}
.top-scroll {
  margin-top: 50px;
}
</style>

ОБРАТИТЕ ВНИМАНИЕ наheight: 12px.. Я сделал его 12px, чтобы его заметили и пользователи Mac. Но с окнами достаточно 0,1 пикселя

Угловая версия

Я объединил здесь 2 ответа. (@simo и @bresleveloper)

https://stackblitz.com/edit/angular-double-scroll?file=src%2Fapp%2Fapp.component.html

встроенный компонент

@Component({
  selector: 'app-double-scroll',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="wrapper1" #wrapper1>
      <div class="div1" #div1></div>
    </div>
    <div class="wrapper2" #wrapper2>
        <div class="div2" #div2>
            <ng-content></ng-content>
        </div>
    </div>
  `,
  styles: [
    `
      .wrapper1, .wrapper2 { width: 100%; overflow-x: auto; overflow-y: hidden; }
    `,
    `
      .div1 { overflow: hidden; height: 0.5px;}
    `,
    `
      .div2 { overflow: hidden; min-width: min-content}
    `
  ]
})
export class DoubleScrollComponent implements AfterViewInit {

  @ViewChild('wrapper1') wrapper1: ElementRef<any>;
  @ViewChild('wrapper2') wrapper2: ElementRef<any>;

  @ViewChild('div1') div1: ElementRef<any>;
  @ViewChild('div2') div2: ElementRef<any>;

  constructor(private _r: Renderer2, private _cd: ChangeDetectorRef) {
  }


  ngAfterViewInit() {

    this._cd.detach();

    this._r.setStyle(this.div1.nativeElement, 'width', this.div2.nativeElement.clientWidth + 'px' );

    this.wrapper1.nativeElement.onscroll = e => this.wrapper2.nativeElement.scroll((e.target as HTMLElement).scrollLeft, 0)
    this.wrapper2.nativeElement.onscroll = e => this.wrapper1.nativeElement.scroll((e.target as HTMLElement).scrollLeft, 0)

  }

}

пример

<div style="width: 200px; border: 1px black dashed">

  <app-double-scroll>
    <div style="min-width: 400px; background-color: red; word-break: keep-all; white-space: nowrap;">
      long ass text long ass text long ass text long ass text long ass text long ass text long ass text long ass text long ass text 
    </div>
  </app-double-scroll>

  <br>
  <hr>
  <br>

  <app-double-scroll>
    <div style="display: inline-block; background-color: green; word-break: keep-all; white-space: nowrap;">
      short ass text
    </div>
  </app-double-scroll>

  <br>
  <hr>
  <br>

  <app-double-scroll>
    <table width="100%" border="0" cellspacing="0" cellpadding="0">
      <tbody>
        <tr>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
          <td>table cell</td>
        </tr>
      </tbody>
    </table>
  </app-double-scroll>

</div>

Расширяя ответ Стэнли и пытаясь найти необходимый минимум, вот что я реализовал:

JavaScript (вызывается один раз откуда-то вроде $ (document).ready ()):

function doubleScroll(){
        $(".topScrollVisible").scroll(function(){
            $(".tableWrapper")
                .scrollLeft($(".topScrollVisible").scrollLeft());
        });
        $(".tableWrapper").scroll(function(){
            $(".topScrollVisible")
                .scrollLeft($(".tableWrapper").scrollLeft());
        });
}

HTML (обратите внимание, что ширина будет изменять длину полосы прокрутки):

<div class="topScrollVisible" style="overflow-x:scroll">
    <div class="topScrollTableLength" style="width:1520px; height:20px">
    </div>
</div>
<div class="tableWrapper" style="overflow:auto; height:100%;">
    <table id="myTable" style="width:1470px" class="myTableClass">
...
    </table>

Вот и все.

В ванильном Javascript/Angular вы можете сделать это следующим образом:

scroll() {
    let scroller = document.querySelector('.above-scroller');
    let table = document.querySelector('.table');
    table.scrollTo(scroller.scrollLeft,0);
  }

HTML:

<div class="above-scroller" (scroll)="scroll()">
  <div class="scroller"></div>
</div>
<div class="table" >
  <table></table>
</div>

CSS:

.above-scroller  {
   overflow-x: scroll;
   overflow-y:hidden;
   height: 20px;
   width: 1200px
 }

.scroller {
  width:4500px;
  height: 20px;
}

.table {
  width:100%;
  height: 100%;
  overflow: auto;
}

Это мое решение в vanilla js для простого использования с Bootstrap:

      <div class="table-responsive table-responsive-scrollbar-top"></div>
<div class="table-responsive">
    <table class="table">
    <!-- ... table code ... -->
    </table>
</div>

<script>
    window.addEventListener('DOMContentLoaded', function() {
        let mains = document.querySelectorAll('.table-responsive');
        if (mains.length > 0) {
            Array.from(mains).forEach(function(main) {
                let top = main.previousElementSibling.matches('.table-responsive-scrollbar-top') ? main.previousElementSibling : null;
                if (top) {
                    let timeout = false;
                    let toggleScrollbar;

                    top.style.display = 'none';
                    
                    if (!top.firstElementChild) {
                        top.appendChild(document.createElement("div"));
                    }

                    (toggleScrollbar = function() {
                        
                        if (main.offsetWidth < main.scrollWidth) {
                            top.style.display = 'block';
                            top.style.height = (main.offsetHeight - main.clientHeight) + 'px';
                            top.firstElementChild.style.width = main.scrollWidth + 'px';
                        } else {
                            top.style.display = 'revert';
                        }
                    })();

                    addEventListener('resize', (event) => {
                        clearTimeout(timeout);
                        timeout = setTimeout(toggleScrollbar, 250);
                    });

                    top.addEventListener('scroll', function(e) {
                        main.scrollLeft = top.scrollLeft;
                    });
                    main.addEventListener('scroll', function(e) {
                        top.scrollLeft = main.scrollLeft;
                    });
                }
            });
        }
    });
</script>

https://github.com/lsblsb/bootstrap-table-responsive-scrollbar-top

Всем поклонникам angular/nativeJs, реализующим ответ @simo

HTML (без изменений)

<div class="top-scroll-wrapper">
    <div class="top-scroll"></div>
</div>

CSS (без изменений, ширина: 90% - это мой дизайн)

.top-scroll-wrapper { width: 90%;height: 20px;margin: auto;padding: 0 16px;overflow-x: auto;overflow-y: hidden;}
.top-scroll { height: 20px; }

JS (вроде onload) или ngAfterViewChecked (все as для TypeScript)

let $topscroll = document.querySelector(".top-scroll") as HTMLElement
let $topscrollWrapper = document.querySelector(".top-scroll-wrapper") as HTMLElement
let $table = document.querySelectorAll('mat-card')[3] as HTMLElement

$topscroll.style.width = totalWidth + 'px'
$topscrollWrapper.onscroll = e => $table.scroll((e.target as HTMLElement).scrollLeft, 0)
$table.onscroll = e => $topscrollWrapper.scroll((e.target as HTMLElement).scrollLeft, 0)

Если вы используете iscroll.js в веб-браузере или мобильном браузере, вы можете попробовать:

$('#pageWrapper>div:last-child').css('top', "0px");

Расширение ответа Дарио Дигрегорио /questions/46942503/gorizontalnaya-polosa-prokrutki-sverhu-i-snizu-tablitsyi/55161106#55161106 (опубликовано здесь). Мне удалось переместить полосу прокрутки выше и ниже содержимого, когда нижняя часть контейнера прокручивается в поле зрения или нет.

          setTimeout(()=>{
      const container = document.querySelector('.container');
      const content = document.querySelector('.content');
      handleWindowScroll();

      function handleWindowScroll() {
        const viewportHeight = window.innerHeight;
        const containerRect = container.getBoundingClientRect();
        const containerTop = containerRect.top;
        const containerHeight = containerRect.height;
        const containerBottom = containerTop + containerHeight;

        const scrollThreshold = 100; // adjust this value as needed
        if (containerBottom <= viewportHeight + scrollThreshold) {
          // bottom of container is visible in viewport
          container.style.transform = '';
          content.style.transform = '';
        } else {
          // bottom of container is not visible in viewport
          container.style.transform = 'rotateX(180deg)';
          content.style.transform = 'rotateX(180deg)';
        }
      }

      window.addEventListener('scroll', handleWindowScroll);

    }, 200);        

Обратите внимание, что я использую setTimeout из-за другого компонента, который мне придется немного подождать, поэтому вам, возможно, не понадобится его использовать.

Директива AngularJs для достижения этой цели: чтобы использовать ее, добавьте в свой элемент двойной hscroll класса css. Для этого вам понадобятся jQuery и AngularJs.

import angular from 'angular';

var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
  $scope.name = 'Dual wielded horizontal scroller';
});

app.directive('doubleHscroll', function($compile) {
  return {
restrict: 'C',
link: function(scope, elem, attr){

  var elemWidth = parseInt(elem[0].clientWidth);

  elem.wrap(`<div id='wrapscroll' style='width:${elemWidth}px;overflow:scroll'></div>`); 
  //note the top scroll contains an empty space as a 'trick' 
  $('#wrapscroll').before(`<div id='topscroll' style='height:20px; overflow:scroll;width:${elemWidth}px'><div style='min-width:${elemWidth}px'> </div></div>`);

  $(function(){
    $('#topscroll').scroll(function(){
      $("#wrapscroll").scrollLeft($("#topscroll").scrollLeft());
    });
    $('#wrapscroll').scroll(function() {
      $("#topscroll").scrollLeft($("#wrapscroll").scrollLeft());
    });

  });  

}

  };


});

Я пытался найти ответ наreactверсию, но найти подходящее решение было действительно сложно. Наконец я получил исправление от npm .

React-двойная полоса прокрутки

https://www.npmjs.com/package/react-double-scrollbar

      render() {
    return (
      <div>
        <DoubleScrollbar>
          <table>...</table>
        </DoubleScrollbar>
      </div>
    );
  }

Существует проблема с направлением прокрутки: RTL. Кажется, плагин не поддерживает его правильно. Вот скрипка: jsfiddle.net/qsn7tupc/3

Обратите внимание, что в Chrome он работает правильно, но во всех других популярных браузерах направление верхней полосы прокрутки остается левым.

Другие вопросы по тегам