My target is:
The problem begins when I add text-align: right; to the style: 3 dots behave differently than without the text-align style property: in general, 3 dots are outside the text container (grey color).

Compare "good" vs "bad" styles; The only difference: "good" doesn't contain text-align: right; style property. Try to play with font-size in those styles and you will see that for the "good" style 3 dots are always inside the text container (grey background) while for the "bad" style 3 dots' position is unexpected (could be inside the container, or partly inside, or fully outside)
So, is there any chance to have a 3 dots behavior like for the "good" style, but at the same time have text aligned right? Take into account the line number limit.
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #78b9f3;
margin:0;
font-family: "Arial";
}
.wrapper {
display: flex;
justify-content: center;
}
.card {
background: #fff;
box-shadow: 0 4px 24px 3px rgb(0 0 0 / 10%);
padding: 20px;
margin: 20px;
width: 200px;
height: auto;
border-radius: 6px;
}
p {
font-size: 1em;
line-height: 1.3em;
margin:0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
.good {
font-size: 20px;
background-color: grey;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.bad {
font-size: 20px;
text-align: right;
background-color: grey;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
<section class="wrapper">
<div class="card">
<h2>Good</h2>
<p class="good">Hello, I'm a very long text for at least three lines!</p>
</div>
<div class="card">
<h2>Bad</h2>
<p class="bad">Hello, I'm a very long text for at least three lines!</p>
</div>
</section>
I just found a trick way to fix the above issue:
Hope this way help you temporarily fix the issue.
See example below:
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #78b9f3;
margin:0;
font-family: "Arial";
}
.wrapper {
display: flex;
justify-content: center;
}
.card {
background: #fff;
box-shadow: 0 4px 24px 3px rgb(0 0 0 / 10%);
padding: 20px;
margin: 20px;
width: 200px;
height: auto;
border-radius: 6px;
}
p {
font-size: 1em;
line-height: 1.3em;
margin:0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
.good {
font-size: 20px;
background-color: grey;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
text-align: right;
padding-right: 20px; //added code
}
.bad {
font-size: 20px;
text-align: right;
background-color: grey;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
<section class="wrapper">
<div class="card">
<h2>Good</h2>
<p class="good">Hello, I'm a very long text for at least three lines!</p>
</div>
<div class="card">
<h2>Bad</h2>
<p class="bad">Hello, I'm a very long text for at least three lines!</p>
</div>
</section>
Just take a look at the compatibility table on mdn – apart from firefox, browser support is rather spotty (to put it mildly).
However, you could fix this rendering issue with some javaScript.
... or you could also mimic text-overflow an line-clamp properties with some old school overflow and line height properties.
This hack is based on defining some line-height related classes.
Css variables help to facilitate the calculations:
:root {
--overflowLineHeight: 1.25em;
--overflowColor1: rgba(255, 255, 255, 1);
--overflowColor2: rgba(255, 255, 255, 0);
}
.maxLines2 {
max-height: calc(var(--overflowLineHeight) * 2);
}
The actual overflow indicator (ellipsis) element is just an absolutely positioned pseudo element with a background gradient (placed in a relatively positioned parent element).
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #78b9f3;
margin: 0;
font-family: "Arial";
}
.wrapper {
display: flex;
justify-content: center;
padding:1em;
}
.card {
background: #fff;
box-shadow: 0 4px 24px 3px rgb(0 0 0 / 10%);
padding: 1em;
margin: 1em;
width: 33%;
height: auto;
border-radius: 6px;
resize: both;
overflow: hidden;
}
p {
font-size: 1em;
line-height: 1.3em;
margin: 0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
:root {
--overflowLineHeight: 1.25em;
--overflowColor1: rgba(255, 255, 255, 1);
--overflowColor2: rgba(255, 255, 255, 0);
}
.overflow {
font-size: 20px;
line-height: var(--overflowLineHeight);
text-align: right;
overflow: hidden;
position: relative;
}
.overflow:after {
content: " …";
position: absolute;
bottom: 0;
right: 0;
display: block;
z-index: 1;
background-image: linear-gradient( 90deg, var(--overflowColor2), var(--overflowColor1) 50%);
width: 2.5em;
}
.maxLines2 {
max-height: calc(var(--overflowLineHeight) * 2);
}
.maxLines3 {
max-height: calc(var(--overflowLineHeight) * 3);
}
.maxLines4 {
max-height: calc(var(--overflowLineHeight) * 4);
}
<section class="wrapper">
<div class="card">
<h2>Fix: 2 lines</h2>
<p class=" maxLines2 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 3 lines</h2>
<p class=" maxLines3 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 4 lines</h2>
<p class=" maxLines4 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
</section>
line-clampThis hack is based on the fact word-break: break-all will render the ellipsis within the block boundaries.
Unfortunately, this will result in ugly word breaks.
To fix this issue,
<span>elements.word-break: break-word propertylet overflows = document.querySelectorAll(".overflow");
fixOverflow(overflows);
function fixOverflow(overflows) {
overflows.forEach(function(text, i) {
//split textContent into array
let words = text.innerHTML
.split(" ")
.map((w) => {
return w.trim();
})
.filter(Boolean);
//delete current textContent
let wrapped = "";
text.textContent = "";
text.style.setProperty("word-break", "break-all");
let bbText = text.getBoundingClientRect();
let bottomText = bbText.y + bbText.height;
//get line height by checking the first word's height
let span0 = document.createElement("span");
span0.textContent = words[0] + " ";
span0.classList.add("wordWrp");
text.appendChild(span0);
let height0 = span0.getBoundingClientRect().height;
//check max lines and init counter
let style = window.getComputedStyle(text);
let maxLines = parseFloat(style.webkitLineClamp);
let breaks = 0;
//wrap words in spans
for (let i = 1; i < words.length; i++) {
let word = words[i];
let span = document.createElement("span");
span.textContent = word + " ";
span.classList.add("wordWrp");
text.appendChild(span);
let bbSpan = span.getBoundingClientRect();
let heightSpan = bbSpan.height;
let bottomSpan = bbSpan.y + bbSpan.height;
if (heightSpan > height0 && breaks < maxLines - 1 && bottomSpan < bottomText) {
span.classList.add("wordWrpLine");
breaks++;
}
}
});
}
//update on resize
const resizeObserver = new ResizeObserver(() => {
upDateOverflows();
});
overflows.forEach(function(text, i) {
resizeObserver.observe(text);
});
function upDateOverflows() {
overflows.forEach(function(text, i) {
let wordWraps = text.querySelectorAll(".wordWrp");
let bbText = text.getBoundingClientRect();
let bottom = bbText.y + bbText.height;
let height0 = wordWraps[0].getBoundingClientRect().height;
//check max lines and init counter
let style = window.getComputedStyle(text);
let maxLines = parseFloat(style.webkitLineClamp);
let breaks = 0;
for (let i = 1; i < wordWraps.length; i++) {
let wordWrap = wordWraps[i];
wordWrap.classList.remove("wordWrpLine");
let bb = wordWrap.getBoundingClientRect();
let height = bb.height;
let bottomSpan = bb.y + bb.height;
if (height > height0 && breaks < maxLines - 1 && bottomSpan < bottom) {
wordWrap.classList.add("wordWrpLine");
breaks++;
}
}
});
}
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
background: #78b9f3;
margin: 0;
font-family: "Arial";
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
}
.card {
background: #fff;
box-shadow: 0 4px 24px 3px rgb(0 0 0 / 10%);
padding: 20px;
margin: 20px;
width: 250px;
height: auto;
border-radius: 6px;
resize: both;
overflow: hidden;
}
p {
font-size: 1em;
line-height: 1.3em;
margin: 0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
.overflow {
font-size: 20px;
text-align: right;
background-color: grey;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.overflow:hover .wordWrp {
outline: 1px solid red;
}
.maxLines2 {
-webkit-line-clamp: 2;
line-clamp: 2;
}
.maxLines3 {
-webkit-line-clamp: 3;
line-clamp: 3;
}
.maxLines4 {
-webkit-line-clamp: 4;
line-clamp: 4;
}
.wordWrpLine {
word-break: break-word;
}
.wordWrpLine2 {
background: red;
}
<h2>Resize cards</h2>
<section class="wrapper">
<div class="card">
<h2>Fix: 2 lines</h2>
<p class=" maxLines2 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 3 lines</h2>
<p class=" maxLines3 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 4 lines</h2>
<p class=" maxLines4 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
</section>
Pretty similar to the previous js approach – by means of splitting the element's text into span elements.
This time we're completely replacing the css elllipsis placeholder by appending a custom <span> element.
The overflow "ellipsis" placeholder is placed according to some boundig box checks: the first span element leaving the overflow defined boundaries will be the position/index for prepending the ellipsis span.
let overflows = document.querySelectorAll(".overflow");
fixOverflow(overflows);
function fixOverflow(overflows) {
overflows.forEach(function(text) {
let spanWrps = text.querySelectorAll(".wordWrp");
let words = '';
let hasSpans = spanWrps.length ? true : false;
let spanEllipse = text.querySelector(".spanEllipse");
//create ellpse san if not already present
if (!spanEllipse) {
spanEllipse = document.createElement("span");
spanEllipse.classList.add('spanEllipse');
spanEllipse.textContent = ' …';
}
// get word/string array - unless it's aleady generated
if (hasSpans) {
words = [...spanWrps].map((word) => {
return word.textContent
});
} else {
//split textContent into array
words = text.innerHTML
.split(" ")
.map((w) => {
return w.trim();
})
.filter(Boolean);
}
let bbText = text.getBoundingClientRect();
let bottomText = bbText.y + bbText.height;
//delete current textContent
if (!hasSpans) {
text.textContent = "";
}
for (let i = 0; i < words.length; i++) {
let word = words[i];
//wrap words in spans if not already done
let span = '';
if (hasSpans) {
span = spanWrps[i];
span.classList.remove('wordWrpOverflow');
} else {
span = document.createElement("span");
span.textContent = word + " ";
text.appendChild(span);
span.classList.add("wordWrp");
}
let bbSpan = span.getBoundingClientRect();
let bottomSpan = bbSpan.y + bbSpan.height;
if (bottomSpan > bottomText) {
span.classList.add("wordWrpOverflow");
} else {
span.classList.remove("wordWrpOverflow");
}
}
let firstOverFlow = text.querySelector('.wordWrpOverflow');
if (firstOverFlow) {
let bbE = spanEllipse.getBoundingClientRect();
let bottomE = bbE.y + bbE.height;
let bbPrev = firstOverFlow.previousElementSibling.getBoundingClientRect();
let bottomPrev = bbPrev.y + bbPrev.height;
//add ellipsis before first overflow element
if (bottomE > bottomText && bottomPrev < bottomText) {
firstOverFlow.parentNode.insertBefore(spanEllipse, firstOverFlow.previousElementSibling);
} else {
// ellipsis might be in overflow area - traverse backwards
if (bottomPrev > bottomText) {
firstOverFlow.parentNode.insertBefore(spanEllipse, firstOverFlow.previousElementSibling.previousElementSibling);
} else {
firstOverFlow.parentNode.insertBefore(spanEllipse, firstOverFlow);
}
}
}
// no ellipsis neede - remove it!
else {
spanEllipse.remove();
}
});
}
//update on resize
const resizeObserver = new ResizeObserver(() => {
fixOverflow(overflows)
});
overflows.forEach(function(text, i) {
resizeObserver.observe(text);
});
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
background: #78b9f3;
margin: 0;
font-family: "Arial";
}
.card {
background: #fff;
padding: 20px;
margin: 20px;
height: auto;
border-radius: 6px;
resize: both;
overflow: auto;
}
p {
font-size: 1em;
line-height: 1.3em;
margin: 0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
:root {
--lineHeight: 1.2em;
}
.overflow {
font-size: 20px;
line-height: var(--lineHeight);
text-align: right;
overflow: hidden;
}
.maxLines2 {
max-height: calc(2 * var(--lineHeight));
}
.maxLines3 {
max-height: calc(3 * var(--lineHeight));
}
.maxLines4 {
max-height: calc(4 * var(--lineHeight));
}
.wordWrpOverflow {
visibility: hidden;
}
/* force line break */
.spanEllipse+span {
display: block
}
.txt-cnt * {
text-align: center;
}
@media (min-width:720px) {
.card {
width: 33%;
}
.wrapper {
display: flex;
justify-content: center;
}
}
@media (min-width:1024px) {
.card {
width: 250px;
}
}
<section class="wrapper">
<div class="card">
<h2>Fix: 2 lines</h2>
<p class=" maxLines2 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 3 lines</h2>
<p class=" maxLines3 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card txt-cnt">
<h2>Fix: 4 lines: <br />text-align:center</h2>
<p class=" maxLines4 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
</section>
Currently only firefox and some iOS safari versions seem to render ellipsis correctly.
To avoid unnecessary js processing for these browser you could include a feature detection:
let firefoxOverflow = CSS.supports("text-overflow", "ellipsis ellipsis");
let safariWebkit = CSS.supports("-webkit-hyphens", "none");
let needsWorkaround = false;
if(!firefoxOverflow && !safariWebkit) {
needsWorkaround = true;
document.body.classList.add("needs-overflow-fix");
}
This will certainly not work for all firefox an safari versions.
At least it doesn't use any browser sniffing.
/**
* check proper overflow support
**/
let supportText = "";
let firefoxOverflow = CSS.supports("text-overflow", "ellipsis ellipsis");
let safariWebkit = CSS.supports("-webkit-hyphens", "none");
let needsWorkaround = false;
if (!firefoxOverflow && !safariWebkit) {
needsWorkaround = true;
document.body.classList.add("needs-overflow-fix");
}
// just for display
if (needsWorkaround) {
supportText = "needs workaround - use js";
} else {
supportText = "proper support – use css";
}
support.textContent = supportText;
/**
* apply workaround if necessary
**/
if (needsWorkaround) {
let overflows = document.querySelectorAll(".overflow");
fixOverflow(overflows);
function fixOverflow(overflows) {
overflows.forEach(function (text) {
/*
let style = window.getComputedStyle(text);
let styleClamp = parseFloat(style.getPropertyValue("-webkit-line-clamp"));
styleClamp = !styleClamp
? parseFloat(style.getPropertyValue("line-clamp"))
: styleClamp;
//console.log(style)
console.log("clamp", styleClamp);
if (styleClamp > 0) {
//text.classList.add('maxLines'+styleClamp);
}
*/
let spanWrps = text.querySelectorAll(".wordWrp");
let words = "";
let hasSpans = spanWrps.length ? true : false;
let spanEllipse = text.querySelector(".spanEllipse");
//create ellpse san if not already present
if (!spanEllipse) {
spanEllipse = document.createElement("span");
spanEllipse.classList.add("spanEllipse");
spanEllipse.textContent = " …";
}
// get word/string array - unless it's aleady generated
if (hasSpans) {
words = [...spanWrps].map((word) => {
return word.textContent;
});
} else {
//split textContent into array
words = text.innerHTML
.split(" ")
.map((w) => {
return w.trim();
})
.filter(Boolean);
}
let bbText = text.getBoundingClientRect();
let bottomText = bbText.y + bbText.height;
//delete current textContent
if (!hasSpans) {
text.textContent = "";
}
for (let i = 0; i < words.length; i++) {
let word = words[i];
//wrap words in spans if not already done
let span = "";
if (hasSpans) {
span = spanWrps[i];
span.classList.remove("wordWrpOverflow");
} else {
span = document.createElement("span");
span.textContent = word + " ";
text.appendChild(span);
span.classList.add("wordWrp");
}
let bbSpan = span.getBoundingClientRect();
let bottomSpan = bbSpan.y + bbSpan.height;
if (bottomSpan > bottomText) {
span.classList.add("wordWrpOverflow");
} else {
span.classList.remove("wordWrpOverflow");
}
}
let firstOverFlow = text.querySelector(".wordWrpOverflow");
if (firstOverFlow) {
let bbE = spanEllipse.getBoundingClientRect();
let bottomE = bbE.y + bbE.height;
let bbPrev = firstOverFlow.previousElementSibling.getBoundingClientRect();
let bottomPrev = bbPrev.y + bbPrev.height;
//add ellipsis before first overflow element
if (bottomE > bottomText && bottomPrev < bottomText) {
firstOverFlow.parentNode.insertBefore(
spanEllipse,
firstOverFlow.previousElementSibling
);
} else {
// ellipsis might be in overflow area - traverse backwards
if (bottomPrev > bottomText) {
firstOverFlow.parentNode.insertBefore(
spanEllipse,
firstOverFlow.previousElementSibling.previousElementSibling
);
} else {
firstOverFlow.parentNode.insertBefore(spanEllipse, firstOverFlow);
}
}
}
// no ellipsis neede - remove it!
else {
spanEllipse.remove();
}
});
}
//update on resize
const resizeObserver = new ResizeObserver(() => {
fixOverflow(overflows);
});
overflows.forEach(function (text, i) {
resizeObserver.observe(text);
});
}
* {
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
background: #78b9f3;
margin: 0;
font-family: "Arial";
}
.needs-overflow-fix {
background: orange;
}
.card {
background: #fff;
padding: 20px;
margin: 20px;
height: auto;
border-radius: 6px;
resize: both;
overflow: auto;
}
p {
font-size: 1em;
line-height: 1.3em;
margin: 0;
}
h2 {
font-size: 1.5em;
margin: 0 0 0.5em 0;
}
:root {
--lineHeight: 1.2em;
}
.overflow {
font-size: 20px;
line-height: var(--lineHeight);
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.needs-overflow-fix .overflow {
text-overflow: unset;
overflow: hidden;
display: block;
-webkit-line-clamp: unset !important;
line-clamp: unset !important;
-webkit-box-orient: unset !important;
}
.maxLines2 {
-webkit-line-clamp: 2;
line-clamp: 2;
}
.maxLines3 {
-webkit-line-clamp: 3;
line-clamp: 3;
}
.maxLines4 {
-webkit-line-clamp: 4;
line-clamp: 4;
}
.needs-overflow-fix .maxLines2 {
max-height: calc(2 * var(--lineHeight));
}
.needs-overflow-fix .maxLines3 {
max-height: calc(3 * var(--lineHeight));
}
.needs-overflow-fix .maxLines4 {
max-height: calc(4 * var(--lineHeight));
}
.wordWrpOverflow {
visibility: hidden;
}
/* force line break */
.spanEllipse + span {
display: block;
}
.txt-cnt * {
text-align: center;
}
@media (min-width: 720px) {
.card {
width: 33%;
}
.wrapper {
display: flex;
justify-content: center;
}
}
@media (min-width: 1024px) {
.card {
width: 250px;
}
}
<p style="text-align:center"><strong id="support" > </strong></p>
<section class="wrapper">
<div class="card">
<h2>Fix: 2 lines</h2>
<p class=" maxLines2 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card">
<h2>Fix: 3 lines</h2>
<p class=" maxLines3 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
<div class="card txt-cnt">
<h2>Fix: 4 lines: <br />text-align:center</h2>
<p class="maxLines4 overflow">Hello, I'm a very long text1 for at least three lines! Hello, I'm a very long text2 for at least three lines! Hello, I'm a very long text2 for at least three lines!</p>
</div>
</section>
If the background color changes from blue to orange – js fix is applied.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With