I made a table with the header and the last column fixed using position: sticky
. I want to remove the shadows from the last column when the scroll bar is horizontally at the end, and remove the shadows from the header when the scroll bar is vertically at the beginning. In AntDesign there is an example of the desired result, but in this case the table has shadows only in the fixed columns, not in the header.
Although I don't want solutions with Scroll Event Listener because of performance reasons (see Scroll-linked effects), if someone solves it that way, they can share it for reference and help other people.
The code is also available at CodeSandbox.
main {
display: flex;
max-height: 20rem;
overflow: auto;
}
table {
border-spacing: 0;
}
th {
text-align: left;
}
th,
td {
border-bottom: 1px solid #c6c6c6;
height: 5rem;
white-space: nowrap;
padding-right: 2rem;
}
.fixed {
background-color: white;
position: sticky;
}
.fixed-top {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2);
top: 0;
z-index: 1;
}
.fixed-right {
border-left: 1px solid #c6c6c6;
box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
padding-left: 1rem;
right: 0;
z-index: 1;
}
.fixed-top.fixed-right {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2), -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
z-index: 2;
}
<table>
<thead>
<tr>
<th class="fixed fixed-top">Animal</th>
<th class="fixed fixed-top">Age</th>
<th class="fixed fixed-top">Country</th>
<th class="fixed fixed-top">Sentence</th>
<th class="fixed fixed-top">Color</th>
<th class="fixed fixed-top fixed-right">Programming Language</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sable</td>
<td>15 yo</td>
<td>Japan</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
<td>Purple</td>
<td class="fixed fixed-right">Kotlin</td>
</tr>
<tr>
<td>Toco toucan</td>
<td>35 yo</td>
<td>Brazil</td>
<td>
Sed tortor erat, imperdiet a enim quis, placerat rhoncus nisl.
</td>
<td>Orange</td>
<td class="fixed fixed-right">Swift</td>
</tr>
<tr>
<td>Bull</td>
<td>42 yo</td>
<td>Spain</td>
<td>Donec vitae risus urna.</td>
<td>Red</td>
<td class="fixed fixed-right">JavaScript</td>
</tr>
<tr>
<td>Brown bear</td>
<td>17 yo</td>
<td>Russia</td>
<td>Proin gravida et velit ut congue.</td>
<td>Green</td>
<td class="fixed fixed-right">Python</td>
</tr>
</tbody>
</table>
I made a table with the header and the last column fixed using position: sticky
. I want to remove the shadows from the last column when the scroll bar is horizontally at the end, and remove the shadows from the header when the scroll bar is vertically at the beginning. In AntDesign there is an example of the desired result, but in this case the table has shadows only in the fixed columns, not in the header.
Although I don't want solutions with Scroll Event Listener because of performance reasons (see Scroll-linked effects), if someone solves it that way, they can share it for reference and help other people.
The code is also available at CodeSandbox.
main {
display: flex;
max-height: 20rem;
overflow: auto;
}
table {
border-spacing: 0;
}
th {
text-align: left;
}
th,
td {
border-bottom: 1px solid #c6c6c6;
height: 5rem;
white-space: nowrap;
padding-right: 2rem;
}
.fixed {
background-color: white;
position: sticky;
}
.fixed-top {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2);
top: 0;
z-index: 1;
}
.fixed-right {
border-left: 1px solid #c6c6c6;
box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
padding-left: 1rem;
right: 0;
z-index: 1;
}
.fixed-top.fixed-right {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2), -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
z-index: 2;
}
<table>
<thead>
<tr>
<th class="fixed fixed-top">Animal</th>
<th class="fixed fixed-top">Age</th>
<th class="fixed fixed-top">Country</th>
<th class="fixed fixed-top">Sentence</th>
<th class="fixed fixed-top">Color</th>
<th class="fixed fixed-top fixed-right">Programming Language</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sable</td>
<td>15 yo</td>
<td>Japan</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
<td>Purple</td>
<td class="fixed fixed-right">Kotlin</td>
</tr>
<tr>
<td>Toco toucan</td>
<td>35 yo</td>
<td>Brazil</td>
<td>
Sed tortor erat, imperdiet a enim quis, placerat rhoncus nisl.
</td>
<td>Orange</td>
<td class="fixed fixed-right">Swift</td>
</tr>
<tr>
<td>Bull</td>
<td>42 yo</td>
<td>Spain</td>
<td>Donec vitae risus urna.</td>
<td>Red</td>
<td class="fixed fixed-right">JavaScript</td>
</tr>
<tr>
<td>Brown bear</td>
<td>17 yo</td>
<td>Russia</td>
<td>Proin gravida et velit ut congue.</td>
<td>Green</td>
<td class="fixed fixed-right">Python</td>
</tr>
</tbody>
</table>
Share
Improve this question
edited Jun 17, 2020 at 11:11
Rafael Tavares
asked Jun 8, 2020 at 11:41
Rafael TavaresRafael Tavares
6,4814 gold badges37 silver badges56 bronze badges
0
1 Answer
Reset to default 7First of all, you need to have a CSS class that will remove shadow from the td
and th
elements.
.noShadow {
box-shadow: none !important;
}
Now, there are 2 ways you can do this via javscript:
- Intersection Observer API
- Scroll event
How to do this via Intersection Observer API?
You need to observe two td
elements via two instances of InterSectionObserver
: one instance to observe one td
element each. When the observed td
elements e into view, we want to add noShadow
CSS class to the table row or column. Similarly, noShadow
class will be removed when observed td
elements move out of the view.
I have added observed-column
class on one of the td
elements that will be observed for adding or removing shadow from last column and added observed-row
class on one of the td
elements to add or remove shadow from the header.
Here's the javascript code you need
const lastColumn = document.querySelectorAll(".fixed-right");
const header = document.querySelectorAll(".fixed-top");
const tableContainer = document.querySelector("main");
let rowHasShadow = false;
let columnHasShadow = true;
const options = {
root: tableContainer,
rootMargin: "0px",
threshold: 1
};
const rowObserver = new IntersectionObserver((entries, observer) => {
if (entries[0].isIntersecting) {
// '.observed-row' element is in view, remove shadow from header
rowHasShadow = false;
header.forEach(th => {
if (th.classList.contains('fixed-header') && !columnHasShadow) {
th.classList.add("noShadow")
} else if (!th.classList.contains('fixed-header')) {
th.classList.add("noShadow")
}
});
}
else {
// '.observed-row' element is not in view, add shadow to header
rowHasShadow = true;
header.forEach(th => th.classList.remove("noShadow"));
}
}, options);
rowObserver.observe(document.querySelector(".observed-row"));
const columnObserver = new IntersectionObserver((entries, observer) => {
if (entries[0].isIntersecting) {
// '.observed-column' element is in view, remove shadow from last column
columnHasShadow = false;
lastColumn.forEach(th => {
if (th.classList.contains('fixed-header') && !rowHasShadow) {
th.classList.add("noShadow");
} else if (!th.classList.contains('fixed-header')) {
th.classList.add("noShadow");
}
});
}
else {
// '.observed-column' element is not in view, add shadow to last column
columnHasShadow = true;
lastColumn.forEach(th => th.classList.remove("noShadow"));
}
}, options);
columnObserver.observe(document.querySelector(".observed-column"));
Demo
const lastColumn = document.querySelectorAll(".fixed-right");
const header = document.querySelectorAll(".fixed-top");
const tableContainer = document.querySelector("main");
let rowHasShadow = false;
let columnHasShadow = true;
const options = {
root: tableContainer,
rootMargin: "0px",
threshold: 1
};
const rowObserver = new IntersectionObserver((entries, observer) => {
if (entries[0].isIntersecting) {
// '.observed-row' element is in view, remove shadow from header
rowHasShadow = false;
header.forEach(th => {
if (th.classList.contains('fixed-header') && !columnHasShadow) {
th.classList.add("noShadow")
} else if (!th.classList.contains('fixed-header')) {
th.classList.add("noShadow")
}
});
}
else {
// '.observed-row' element is not in view, add shadow to header
rowHasShadow = true;
header.forEach(th => th.classList.remove("noShadow"));
}
}, options);
rowObserver.observe(document.querySelector(".observed-row"));
const columnObserver = new IntersectionObserver((entries, observer) => {
if (entries[0].isIntersecting) {
// '.observed-column' element is in view, remove shadow from last column
columnHasShadow = false;
lastColumn.forEach(th => {
if (th.classList.contains('fixed-header') && !rowHasShadow) {
th.classList.add("noShadow");
} else if (!th.classList.contains('fixed-header')) {
th.classList.add("noShadow");
}
});
}
else {
// '.observed-column' element is not in view, add shadow to last column
columnHasShadow = true;
lastColumn.forEach(th => th.classList.remove("noShadow"));
}
}, options);
columnObserver.observe(document.querySelector(".observed-column"));
main {
display: flex;
max-height: 20rem;
overflow: auto;
}
table {
border-spacing: 0;
}
th {
text-align: left;
}
th,
td {
border-bottom: 1px solid #c6c6c6;
height: 5rem;
white-space: nowrap;
padding-right: 2rem;
}
.fixed {
background-color: white;
position: sticky;
}
.fixed-top {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2);
top: 0;
z-index: 2;
}
.fixed-right {
border-left: 1px solid #c6c6c6;
box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
padding-left: 1rem;
right: 0;
z-index: 1;
}
.fixed-top.fixed-right {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2),
-5px 0px 5px 0px rgba(0, 0, 0, 0.2);
z-index: 2;
}
.noShadow {
box-shadow: none !important;
}
<main>
<table>
<thead>
<tr>
<th class="fixed fixed-top">Animal</th>
<th class="fixed fixed-top">Age</th>
<th class="fixed fixed-top">Country</th>
<th class="fixed fixed-top">Sentence</th>
<th class="fixed fixed-top observed-column">Color</th>
<th class="fixed fixed-top fixed-right fixed-header">Programming Language</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sable</td>
<td>15 yo</td>
<td>Japan</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
<td>Purple</td>
<td class="fixed fixed-right observed-row">Kotlin</td>
</tr>
<tr>
<td>Toco toucan</td>
<td>35 yo</td>
<td>Brazil</td>
<td>
Sed tortor erat, imperdiet a enim quis, placerat rhoncus nisl.
</td>
<td>Orange</td>
<td class="fixed fixed-right">Swift</td>
</tr>
<tr>
<td>Bull</td>
<td>42 yo</td>
<td>Spain</td>
<td>Donec vitae risus urna.</td>
<td>Red</td>
<td class="fixed fixed-right">JavaScript</td>
</tr>
<tr>
<td>Brown bear</td>
<td>17 yo</td>
<td>Russia</td>
<td>Proin gravida et velit ut congue.</td>
<td>Green</td>
<td class="fixed fixed-right">Python</td>
</tr>
</tbody>
</table>
</main>
You can also view this demo on codesandbox
How to do this via Scroll event?
Add a scroll event listener on main
element. Inside the event handler, you need to check 2 things:
if
scrollLeft
is equal toscrollWidth - clientWidth
, add the.noShadow
class on all thetd
elements in last columnif
scrollTop
is equal to zero, add.noShadow
class on allth
elements
Following is the javascript code you need along with the noShadow
css class
const lastColumn = document.querySelectorAll('.fixed-right');
const header = document.querySelectorAll('.fixed-top');
const tableContainer = document.querySelector('main');
tableContainer.addEventListener('scroll', (e) => {
removeShadowFromColumn();
removeShadowFromHeader();
});
function removeShadowFromHeader() {
if (tableContainer.scrollTop === 0) {
header.forEach(th => th.classList.add('noShadow'));
} else {
header.forEach(th => th.classList.remove('noShadow'));
}
}
function removeShadowFromColumn() {
const widthDiff = tableContainer.scrollWidth - tableContainer.clientWidth;
if (tableContainer.scrollLeft === widthDiff) {
lastColumn.forEach(th => th.classList.add('noShadow'));
} else {
lastColumn.forEach(td => td.classList.remove('noShadow'));
}
}
removeShadowFromHeader();
Demo
const lastColumn = document.querySelectorAll('.fixed-right');
const header = document.querySelectorAll('.fixed-top');
const tableContainer = document.querySelector('main');
tableContainer.addEventListener('scroll', (e) => {
removeShadowFromColumn();
removeShadowFromHeader();
});
function removeShadowFromHeader() {
if (tableContainer.scrollTop === 0) {
header.forEach(th => th.classList.add('noShadow'));
} else {
header.forEach(th => th.classList.remove('noShadow'));
}
}
function removeShadowFromColumn() {
const widthDiff = tableContainer.scrollWidth - tableContainer.clientWidth;
if (tableContainer.scrollLeft === widthDiff) {
lastColumn.forEach(th => th.classList.add('noShadow'));
} else {
lastColumn.forEach(td => td.classList.remove('noShadow'));
}
}
removeShadowFromHeader();
main {
display: flex;
max-height: 20rem;
overflow: auto;
}
table {
border-spacing: 0;
}
th {
text-align: left;
}
th,
td {
border-bottom: 1px solid #c6c6c6;
height: 5rem;
white-space: nowrap;
padding-right: 2rem;
}
.fixed {
background-color: white;
position: sticky;
}
.fixed-top {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2);
top: 0;
z-index: 1;
}
.fixed-right {
border-left: 1px solid #c6c6c6;
box-shadow: -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
padding-left: 1rem;
right: 0;
z-index: 1;
}
.fixed-top.fixed-right {
box-shadow: 4px 3px 5px 0px rgba(0, 0, 0, 0.2), -5px 0px 5px 0px rgba(0, 0, 0, 0.2);
z-index: 2;
}
.noShadow {
box-shadow: none !important;
}
<main>
<table>
<thead>
<tr>
<th class="fixed fixed-top">Animal</th>
<th class="fixed fixed-top">Age</th>
<th class="fixed fixed-top">Country</th>
<th class="fixed fixed-top">Sentence</th>
<th class="fixed fixed-top">Color</th>
<th class="fixed fixed-top fixed-right">Programming Language</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sable</td>
<td>15 yo</td>
<td>Japan</td>
<td>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</td>
<td>Purple</td>
<td class="fixed fixed-right">Kotlin</td>
</tr>
<tr>
<td>Toco toucan</td>
<td>35 yo</td>
<td>Brazil</td>
<td>
Sed tortor erat, imperdiet a enim quis, placerat rhoncus nisl.
</td>
<td>Orange</td>
<td class="fixed fixed-right">Swift</td>
</tr>
<tr>
<td>Bull</td>
<td>42 yo</td>
<td>Spain</td>
<td>Donec vitae risus urna.</td>
<td>Red</td>
<td class="fixed fixed-right">JavaScript</td>
</tr>
<tr>
<td>Brown bear</td>
<td>17 yo</td>
<td>Russia</td>
<td>Proin gravida et velit ut congue.</td>
<td>Green</td>
<td class="fixed fixed-right">Python</td>
</tr>
</tbody>
</table>
</main>
You can also view the demo on codesandbox
P.S. For performance reasons, i suggest you do this via first approach that uses Intersection Observer API.