'Display as much elements as it can fit in one line, and if not all of them can fit add additional element that represents number of remaining elements
I would like to align all tags
for a given article in one line.
If all the tags
can fit in one line, then I want to display all of them, like this:
But, if there is more tags
and not all of them can fit in the same line, I would like to display as much of them as it can fit, and I want to add additional label that represents number of remaining tags
, like this:
I created this quick start code so everyone can jump and update what is necessary.
.card {
padding: 5px;
width: 300px;
height: 300px;
border: 2px solid blue;
display: flex;
flex-direction: row;
gap: 5px;
}
.tag {
padding: 5px 8px;
color: #2C3C93;
background: rgb(44, 60, 147, 0.1);
height: 20px;
}
<div class="card">
<div class="tag">Developing</div>
<div class="tag">Busniess</div>
<div class="tag">Web Design</div>
</div>
Solution 1:[1]
Try this
let cardWidth = document.querySelector('.card').offsetWidth; // or 300
let tagsWidth = 0;
let hiddenItems = 0;
document.querySelectorAll('.tag').forEach(tag => {
tagsWidth += tag.offsetWidth;
if( tagsWidth + 60 > cardWidth ){
hiddenItems++;
tag.style.display = 'none';
}
})
if( hiddenItems > 0 ){
document.querySelector('.card').insertAdjacentHTML('beforeend', `<div class="tag">${hiddenItems}+</div>`)
}
*,*::before,*::after {
box-sizing: border-box;
}
.card {
padding: 5px;
width: 300px;
height: 300px;
border: 2px solid blue;
display: flex;
align-items: flex-start;
gap: 5px;
}
.tag {
padding: 5px 8px;
color: #2C3C93;
background: rgb(44, 60, 147, 0.1);
white-space: nowrap;
}
<div class="card">
<div class="tag">Developing</div>
<div class="tag">Busniess</div>
<div class="tag">Web Design</div>
</div>
Please note that the 60
here tagsWidth + 60
is the width of the "remaining/hidden tags div". This is to ensure that there is enough room for the "remaining/hidden tags div" if the tagsWidth
exceeds the cardWidth
.
Solution 2:[2]
Here is my attempt: https://jsfiddle.net/oneuj3yv/28/
const counterElementWidth = 30;
function collapseTags() {
document.querySelectorAll('.card').forEach(card => {
let counterTag = card.querySelector('.counter-tag');
let cardStyle = getComputedStyle(card);
let padding = parseFloat(cardStyle.paddingLeft) + parseFloat(cardStyle.paddingRight);
let innerWidth = card.clientWidth - padding;
let usedWidth = 0;
let tagsLeft = 0;
card.querySelectorAll('.tag').forEach(tag=>{
usedWidth += tag.getBoundingClientRect().width;
if(usedWidth >= innerWidth - counterElementWidth) {
tag.classList.add('hidden');
tagsLeft++;
} else {
tag.classList.remove('hidden');
}
});
if(tagsLeft > 0) {
counterTag.classList.remove('hidden');
counterTag.innerHTML = `+${tagsLeft}`;
} else {
counterTag.classList.add('hidden');
}
});
}
collapseTags();
window.addEventListener('resize', collapseTags);
For the whole thing to work I have added some CSS and additional HTML to the fiddle as well.
It still seems a bit hit-or-miss on resize, but on load it works.
An improvement could be for the counter element to be dynamically generated.
Another future improvement could be the tweaking of counterElementWidth
I used for the width of the counter (currently I just take a wild guess of 30 px).
Solution 3:[3]
You can use this:
const card = document.querySelector('.card');
const getWidth = (el) => {
return el.getBoundingClientRect().width;
}
const widthCard = getWidth(card);
const tags = [...document.querySelectorAll('.tag')];
const totalWidth = tags.reduce((total, currentEl) => getWidth(currentEl) + total, 0);
if (totalWidth > widthCard) {
const numberTag = document.createElement('div');
numberTag.classList.add('tag');
numberTag.textContent = tags.length + '+';
card.appendChild(numberTag);
const newElements = [];
const remainingTags = [];
let newTotal = getWidth(numberTag) + 19 // +19 for the padding and border and a gap;
for (let tag of tags) {
const width = getWidth(tag);
if ((newTotal + width) < widthCard) {
newElements.push(tag);
newTotal += width + 5; // +5 for spacing
} else {
remainingTags.push(tag);
}
}
numberTag.textContent = `${tags.length - newElements.length}+`;
newElements.push(numberTag);
card.innerHTML = '';
newElements.forEach((el) => {
card.appendChild(el);
})
//You can use the remanining tags to show a tooltip, like this:
numberTag.setAttribute('title', remainingTags.map(e => e.textContent).join(', '));
}
const card = document.querySelector('.card');
const getWidth = (el) => {
return el.getBoundingClientRect().width;
}
const widthCard = getWidth(card);
const tags = [...document.querySelectorAll('.tag')];
const totalWidth = tags.reduce((total, currentEl) => getWidth(currentEl) + total, 0);
if (totalWidth > widthCard) {
const numberTag = document.createElement('div');
numberTag.classList.add('tag');
numberTag.textContent = tags.length + '+';
card.appendChild(numberTag);
const newElements = [];
const remainingTags = [];
let newTotal = getWidth(numberTag) + 19 // +19 for the padding and border and a gap;
for (let tag of tags) {
const width = getWidth(tag);
if ((newTotal + width) < widthCard) {
newElements.push(tag);
newTotal += width + 5; // +5 for spacing
} else {
remainingTags.push(tag);
}
}
numberTag.textContent = `${tags.length - newElements.length}+`;
newElements.push(numberTag);
card.innerHTML = '';
newElements.forEach((el) => {
card.appendChild(el);
})
//You can use the remanining tags to show a tooltip, like this:
numberTag.setAttribute('title', remainingTags.map(e => e.textContent).join(', '));
}
*,*::before,*::after {
box-sizing: border-box;
}
.card {
padding: 5px;
width: 300px;
height: 300px;
border: 2px solid blue;
display: flex;
flex-direction: row;
gap: 5px;
}
.tag {
padding: 5px 8px;
color: #2C3C93;
background: rgb(44, 60, 147, 0.1);
height: fit-content;
white-space: nowrap;
}
<div class="card">
<div class="tag">Developing</div>
<div class="tag">This is a very large tag</div>
<div class="tag">Busniess1</div>
<div class="tag">Web Design</div>
<div class="tag">Web Design</div>
<div class="tag">Web Design</div>
</div>
Also, you have to use a box-sizing of border-box for the elements.
You can see the fiddle here: https://jsfiddle.net/Tanay861/4ezkr2og/41/
Solution 4:[4]
You can create a custom element that does the following:
- Layout content elements as flex rows, by setting its height, it may be used to just show a single row or a max number of rows.
- Detect resize, and hide all overflowing content elements
- Position an info element when content elements do not fit
Example: https://jsfiddle.net/bkep1n2q/1/
(Try different values of max-height on <app-tags style="max-height: 80px;">
, and resize the window)
JS:
export class Tags extends HTMLElement {
#observer = null;
#badge = document.createElement('span');
connectedCallback(){
this.#observer = new ResizeObserver(this.#update.bind(this));
this.#observer.observe(this);
this.#badge.classList.add('app-tags-badge');
this.appendChild(this.#badge);
}
disconnectedCallback(){
this.#observer?.disconnect();
}
#update(){
const p = this.getBoundingClientRect();
let hidden = 0;
let lastVisible = null;
this.childNodes.forEach(n => {
if(n instanceof HTMLElement && n !== this.#badge){
const c = n.getBoundingClientRect();
if(p.left > c.left || p.right < c.right || p.top > c.top || p.bottom < c.bottom){
hidden++;
n.style.visibility = 'hidden';
}else {
n.style.visibility = '';
lastVisible = n;
}
}
});
if(hidden < 1){
this.#badge.style.display = 'none';
}else if(lastVisible !== null){
lastVisible.style.visibility = 'hidden';
this.#badge.style.display = '';
this.#badge.style.top = `${lastVisible.offsetTop}px`;
this.#badge.style.left = `${lastVisible.offsetLeft}px`;
}else {
this.#badge.style.display = '';
this.#badge.style.top = '0';
this.#badge.style.left = '0';
}
this.#badge.innerHTML=`+${hidden} more`;
}
}
window.customElements.define('app-tags',Tags);
CSS:
app-tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
overflow: hidden;
gap: 12px;
border: 1px solid red;
}
app-tags > * {
min-width: 0;
}
app-tags > .app-tags-badge {
position: absolute;
padding: 6px;
}
.card {
padding:6px;
border: 1px solid #ccc;
border-radius: 6px;
}
Usage:
<!DOCTYPE html>
<html>
<body>
<app-tags style="max-height: 80px;">
<span class="card">Card 1</span>
<span class="card">Card 2</span>
<span class="card">Card 3</span>
<span class="card">Card 4</span>
<span class="card">Card 5</span>
<span class="card">Card 6</span>
<span class="card">Card 7</span>
<span class="card">Card 8</span>
<span class="card">Card 9</span>
<span class="card">Card 10</span>
</app-tags>
</body>
</html>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Jelmertje |
Solution 3 | |
Solution 4 |