Issue
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>
Solution
First 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 2 td elements via two instances of InterSectionObserver, one instance to observe one td elemenet, and execute the code, that will remove or add noShadow CSS class from the table row or column, when the observed td elements come in to view.
I have added observed-column class on one of the td element that will be observed for adding or removing shadow from last column and added observed-row class on one of the td element 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
scrollLeftis equal toscrollWidth - clientWidth, add the.noShadowclass on all thetdelements in last columnif
scrollTopis equal to zero, add.noShadowclass on allthelements
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.
Answered By - Yousaf
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.