表格顶部和底部的水平滚动条

我的页面上有一个非常大的table。所以我决定在桌子的底部放一个水平滚动条。但我希望这个滚动条也在桌子的顶部。

我的模板是这样的:

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

这可能只在HTML和CSS中实现吗?

328833 次浏览

据我所知,这在HTML和CSS中是不可能的。

要模拟元素顶部的第二个水平滚动条,请在具有水平滚动的元素上方放置一个__0的“虚拟”__ABC,其高度刚好足以显示滚动条。然后为虚拟元素和真实元素附加“滚动”事件的处理程序,以便在移动任一滚动条时使另一个元素同步。虚拟元素看起来就像真实元素上方的第二个水平滚动条。

一个活生生的例子,请参阅这把小提琴

这是密码

“ 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());
});
});

尝试使用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>

如果您在WebKit浏览器或移动浏览器上使用iscroll.JS,您可以尝试:

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

您可以使用jQuery插件来完成这项工作:

  • https://github.com/avianey/jqdoublescroll.

插件将为您处理所有的逻辑。

首先,回答得很好,@斯坦利。如果有人想知道如何制作具有动态宽度的双卷轴容器:

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/.

基于@StanleyH解决方案,我创建了一个AngularJS指令,在Jsfiddle上演示。

易于使用:

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

对于AngularJS开发人员

扩展Stanleyh的答案,并尝试找到所需的最小值,下面是我实现的:

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>

就这样了。

链接滚动条是可行的,但它的编写方式会创建一个循环,如果您单击较轻的滚动条的一部分并按住它(而不是在拖动滚动条时),则会使大多数浏览器中的滚动速度变慢。

我用旗子把它固定下来:

$(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;
}
});
});

没有jQuery(2017)

因为您可能不需要jQuery,下面是一个基于@StanleyH的普通JS版本。答案:

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>

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

斯坦利的回答非常好,但它有一个不幸的错误:单击滚动条的阴影区域不再跳转到您单击的选择。相反,您得到的是滚动条位置的一个非常小且有些恼人的增量。

测试:4个版本的Firefox(100%受影响),4个版本的Chrome(50%受影响)。

这是我的Jsfiddle。您可以通过一个on/off(true/false)变量来解决这个问题,该变量一次只允许触发一个onScroll()事件:

var scrolling = false;
$(".wrapper1").scroll(function(){
if(scrolling) {
scrolling = false;
return true;
}
scrolling = true;
$(".wrapper2")
.scrollLeft($(".wrapper1").scrollLeft());
});
$(".wrapper2").scroll(function(){
if(scrolling) {
scrolling = false;
return true;
}
scrolling = true;
$(".wrapper1")
.scrollLeft($(".wrapper2").scrollLeft());
});

接受答案的问题行为:

实际期望的行为:

那么,为什么会发生这种情况呢?如果你运行代码,你会看到wrapper1调用wrapper2的scrollLeft,wrapper2调用wrapper1的scrollLeft,并且无限重复,所以,我们有一个无限循环的问题。或者,更确切地说:用户的继续滚动与WrapperX对滚动的调用相冲突,发生了事件冲突,最终结果是滚动条中没有跳转。

希望这能帮助其他人!

基于@HoldoffHunger和@Bobince答案的纯JavaScript解决方案

<div id="doublescroll">
function doubleScroll(element) {
const 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"));
let running = false;
// Keep scrollbar in sync when element size changes
new ResizeObserver(() => {
scrollbar.firstChild.style.width = element.scrollWidth + "px";
}).observe(element);
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("data"));

致所有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)
实现这一目标

的AngularJS指令: 要使用它,请将CSS类double-hscroll添加到元素中。为此,您需要使用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());
});


});


}


};




});

下面是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用户也会注意到它。但在Windows上,0.1px就足够了。

仅使用CSS的解决方案

有一种方法可以实现这一点,我在这里没有看到任何人提到。

通过将父容器旋转180度,并将子容器再次180度,滚动条将显示在顶部

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

有关参考,请参阅W3C存储库中的问题。

有角度的版本

我在这里结合了两个答案。(@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>

反应+打字稿 RAP-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>
);
}

这是我在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.