Reputation: 10030
I have got a span with dynamic data in my page, with ellipsis
style.
.my-class
{
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 71px;
}
<span id="myId" class="my-class"></span>
document.getElementById('myId').innerText = "...";
I'd like to add to this element tooltip with the same content, but I want it to appear only when the content is long and the ellipsis appear on screen.
Is there any way to do it?
Does the browser throw an event when ellipsis
is activated?
*Browser: Internet Explorer
Upvotes: 301
Views: 289015
Reputation: 301
import { FC, HTMLAttributes } from 'react'
import { cn } from 'src/lib/utils/classNames'
export const AutoTooltip: FC<HTMLAttributes<HTMLDivElement>> = ({
className,
children,
}) => {
return (
<div
className={cn('truncate', className)}
onMouseEnter={({ currentTarget }) => {
if (currentTarget.scrollWidth > currentTarget.clientWidth) {
currentTarget.title = currentTarget.innerText
}
}}
>
{children}
</div>
)
}
add ellipsis class to component:
.truncate {
text-overflow: ellipsis;
overflow: hidden
white-space: nowrap;
}
use
<AutoTooltip>
some text you want to show in tooltip if width is overflow...
</AutoTooltip>
Upvotes: 1
Reputation: 383
Reusable solution for React
// useTitleWhenTruncated.ts
import { HTMLAttributes, useState } from 'react';
type TitleProps = Pick<HTMLAttributes<HTMLElement>, 'title' | 'onMouseEnter'>;
export function useTitleWhenTruncated(title: string): TitleProps {
const [isTruncated, setIsTruncated] = useState(false);
return {
title: isTruncated ? title : undefined,
onMouseEnter: event => {
setIsTruncated(
event.currentTarget.offsetWidth < event.currentTarget.scrollWidth,
);
},
};
}
// usage example
const SampleComponent = ({ label }: { label: string }) => {
const titleProps = useTitleWhenTruncated(label);
return (
<div
style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}
{...titleProps}
>
{label}
</div>
);
}
Upvotes: 2
Reputation: 449
In my case i used not only text-overflow: ellipsis;
but also line-clamp
property, so the ellipsis was applied only after the second line of text. Here is the styles of my div:
.text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
I'm using React here, but i think that approach can be applied to vanilla js too. Here it is:
const MyComponent = ({text}: {text: string}) => {
const [titleActive, setTitleActive] = useState(false)
const ref = useCallback<(node: HTMLDivElement) => void>(
(node) => {
if (node) {
const blockWidth = node.offsetWidth
/* setting width to `max-content` makes div's width equal to text
rendered in one line */
node.style.width = 'max-content';
const textWidth = node.offsetWidth;
node.style.width = 'auto';
/* the multiplier of `blockWidth` is the number of lines shown
before ellipsis applied. In my case this is 2 (same
as in `-webkit-line-clamp` css property).
For one condition will be just `(textWidth > blockWidth)`
*/
if (textWidth > blockWidth * 2) {
setTitleActive(true)
}
}
},
[]
);
return (
<div
className="text"
title={titleActive ? text : undefined}
ref={ref}
>
{text}
</div>
)
}
Upvotes: 0
Reputation: 1613
Adding simple one-off solution for year 2022, when you should not use jQuery, and should not try to support discontinued products (like IE 🤮):
document.querySelectorAll('.overflow').forEach(span => {
if (span.scrollWidth > span.clientWidth) {
span.title = span.innerText
}
})
Try it out: https://jsfiddle.net/metalim/cxw26smg/
Upvotes: 7
Reputation: 4015
Here's a Vanilla JavaScript solution:
var cells = document.getElementsByClassName("cell");
for(const cell of cells) {
cell.addEventListener('mouseenter', setTitleIfNecessary, false);
}
function setTitleIfNecessary() {
if(this.offsetWidth < this.scrollWidth) {
this.setAttribute('title', this.innerHTML);
}
}
.cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 1px;
border-style: solid;
width: 120px;
}
<div class="cell">hello world!</div>
<div class="cell">hello mars! kind regards, world</div>
Upvotes: 15
Reputation: 1335
This is what I ended up doing based on https://stackoverflow.com/a/13259660/1714951 and adding the removal of the title attribute when the element doesn't overflow anymore. This is pure JS (ES6), uses event delegation for performance (when using several elements with the same class) and guard clauses for readability:
// You may want to change document by an element closer to your target
// so that the handler is not executed as much
document.addEventListener( 'mouseenter', event => {
let eventTarget = event.target;
// This could be easily applied to text-truncate instead of my-class if you use bootstrap
if ( !eventTarget.classList?.contains( 'my-class' ) ) {
return;
}
if ( eventTarget.offsetWidth < eventTarget.scrollWidth ) {
eventTarget.setAttribute( 'title', eventTarget.innerText );
return;
}
eventTarget.removeAttribute( 'title' );
}, true );
Upvotes: 4
Reputation: 1668
This was my solution, works as a charm!
$(document).on('mouseover', 'input, span', function() {
var needEllipsis = $(this).css('text-overflow') && (this.offsetWidth < this.scrollWidth);
var hasNotTitleAttr = typeof $(this).attr('title') === 'undefined';
if (needEllipsis === true) {
if(hasNotTitleAttr === true){
$(this).attr('title', $(this).val());
}
}
if(needEllipsis === false && hasNotTitleAttr == false){
$(this).removeAttr('title');
}
});
Upvotes: 4
Reputation: 1208
Here are two other pure CSS solutions:
.overflow {
overflow: hidden;
-ms-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
}
.overflow:hover {
overflow: visible;
}
.overflow:hover span {
position: relative;
background-color: white;
box-shadow: 0 0 4px 0 black;
border-radius: 1px;
}
<div>
<span class="overflow" style="float: left; width: 50px">
<span>Long text that might overflow.</span>
</span>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad recusandae perspiciatis accusantium quas aut explicabo ab. Doloremque quam eos, alias dolore, iusto pariatur earum, ullam, quidem dolores deleniti perspiciatis omnis.
</div>
.wrap {
position: relative;
}
.overflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events:none;
}
.overflow:after {
content:"";
display: block;
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 15px;
z-index: 1;
border: 1px solid red; /* for visualization only */
pointer-events:initial;
}
.overflow:hover:after{
cursor: pointer;
}
.tooltip {
/* visibility: hidden; */
display: none;
position: absolute;
top: 10;
left: 0;
background-color: #fff;
padding: 10px;
-webkit-box-shadow: 0 0 50px 0 rgba(0,0,0,0.3);
opacity: 0;
transition: opacity 0.5s ease;
}
.overflow:hover + .tooltip {
/*visibility: visible; */
display: initial;
transition: opacity 0.5s ease;
opacity: 1;
}
<div>
<span class="wrap">
<span class="overflow" style="float: left; width: 50px">Long text that might overflow</span>
<span class='tooltip'>Long text that might overflow.</span>
</span>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad recusandae perspiciatis accusantium quas aut explicabo ab. Doloremque quam eos, alias dolore, iusto pariatur earum, ullam, quidem dolores deleniti perspiciatis omnis.
</div>
Upvotes: 74
Reputation: 2258
I created a jQuery plugin that uses Bootstrap's tooltip instead of the browser's build-in tooltip. Please note that this has not been tested with older browser.
JSFiddle: https://jsfiddle.net/0bhsoavy/4/
$.fn.tooltipOnOverflow = function(options) {
$(this).on("mouseenter", function() {
if (this.offsetWidth < this.scrollWidth) {
options = options || { placement: "auto"}
options.title = $(this).text();
$(this).tooltip(options);
$(this).tooltip("show");
} else {
if ($(this).data("bs.tooltip")) {
$tooltip.tooltip("hide");
$tooltip.removeData("bs.tooltip");
}
}
});
};
Upvotes: 12
Reputation: 425
We need to detect whether ellipsis is really applied, then to show a tooltip to reveal full text. It is not enough by only comparing "this.offsetWidth < this.scrollWidth
" when the element nearly holding its content but only lacking one or two more pixels in width, especially for the text of full-width Chinese/Japanese/Korean characters.
Here is an example: http://jsfiddle.net/28r5D/5/
I found a way to improve ellipsis detection:
this.offsetWidth < this.scrollWidth
" first, continue step #2 if failed.Here is my improvement: http://jsfiddle.net/28r5D/6/
Upvotes: 14
Reputation: 5199
This is what I did. Most tooltip scripts require you to execute a function that stores the tooltips. This is a jQuery example:
$.when($('*').filter(function() {
return $(this).css('text-overflow') == 'ellipsis';
}).each(function() {
if (this.offsetWidth < this.scrollWidth && !$(this).attr('title')) {
$(this).attr('title', $(this).text());
}
})).done(function(){
setupTooltip();
});
If you didn't want to check for ellipsis css, you could simplify like:
$.when($('*').filter(function() {
return (this.offsetWidth < this.scrollWidth && !$(this).attr('title'));
}).each(function() {
$(this).attr('title', $(this).text());
})).done(function(){
setupTooltip();
});
I have the "when" around it, so that the "setupTooltip" function doesn't execute until all titles have been updated. Replace the "setupTooltip", with your tooltip function and the * with the elements you want to check. * will go through them all if you leave it.
If you simply want to just update the title attributes with the browsers tooltip, you can simplify like:
$('*').filter(function() {
return $(this).css('text-overflow') == 'ellipsis';
}).each(function() {
if (this.offsetWidth < this.scrollWidth && !$(this).attr('title')) {
$(this).attr('title', $(this).text());
}
});
Or without check for ellipsis:
$.when($('*').filter(function() {
return (this.offsetWidth < this.scrollWidth && !$(this).attr('title'));
}).each(function() {
$(this).attr('title', $(this).text());
});
Upvotes: 2
Reputation: 12561
You could possibly surround the span with another span, then simply test if the width of the original/inner span is greater than that of the new/outer span. Note that I say possibly -- it is roughly based on my situation where I had a span
inside of a td
so I don't actually know that if it will work with a span
inside of a span
.
Here though is my code for others who may find themselves in a position similar to mine; I'm copying/pasting it without modification even though it is in an Angular context, I don't think that detracts from the readability and the essential concept. I coded it as a service method because I needed to apply it in more than one place. The selector I've been passing in has been a class selector that will match multiple instances.
CaseService.applyTooltip = function(selector) {
angular.element(selector).on('mouseenter', function(){
var td = $(this)
var span = td.find('span');
if (!span.attr('tooltip-computed')) {
//compute just once
span.attr('tooltip-computed','1');
if (span.width() > td.width()){
span.attr('data-toggle','tooltip');
span.attr('data-placement','right');
span.attr('title', span.html());
}
}
});
}
Upvotes: -1
Reputation: 1263
I have CSS class, which determines where to put ellipsis. Based on that, I do the following (element set could be different, i write those, where ellipsis is used, of course it could be a separate class selector):
$(document).on('mouseover', 'input, td, th', function() {
if ($(this).css('text-overflow') && typeof $(this).attr('title') === 'undefined') {
$(this).attr('title', $(this).val());
}
});
Upvotes: 1
Reputation: 10792
Here's a pure CSS solution. No need for jQuery. It won't show a tooltip, instead it'll just expand the content to its full length on mouseover.
Works great if you have content that gets replaced. Then you don't have to run a jQuery function every time.
.might-overflow {
text-overflow: ellipsis;
overflow : hidden;
white-space: nowrap;
}
.might-overflow:hover {
text-overflow: clip;
white-space: normal;
word-break: break-all;
}
Upvotes: 97
Reputation: 4251
Here is my jQuery plugin:
(function($) {
'use strict';
$.fn.tooltipOnOverflow = function() {
$(this).on("mouseenter", function() {
if (this.offsetWidth < this.scrollWidth) {
$(this).attr('title', $(this).text());
} else {
$(this).removeAttr("title");
}
});
};
})(jQuery);
Usage:
$("td, th").tooltipOnOverflow();
Edit:
I have made a gist for this plugin. https://gist.github.com/UziTech/d45102cdffb1039d4415
Upvotes: 18
Reputation: 20818
Here's a way that does it using the built-in ellipsis setting, and adds the title
attribute on-demand (with jQuery) building on Martin Smith's comment:
$('.mightOverflow').bind('mouseenter', function(){
var $this = $(this);
if(this.offsetWidth < this.scrollWidth && !$this.attr('title')){
$this.attr('title', $this.text());
}
});
Upvotes: 186
Reputation: 9
None of the solutions above worked for me, but I figured out a great solution. The biggest mistake people are making is having all the 3 CSS properties declared on the element upon pageload. You have to add those styles+tooltip dynamically IF and ONLY IF the span you want an ellipses on is wider than its parent.
$('table').each(function(){
var content = $(this).find('span').text();
var span = $(this).find('span');
var td = $(this).find('td');
var styles = {
'text-overflow':'ellipsis',
'white-space':'nowrap',
'overflow':'hidden',
'display':'block',
'width': 'auto'
};
if (span.width() > td.width()){
span.css(styles)
.tooltip({
trigger: 'hover',
html: true,
title: content,
placement: 'bottom'
});
}
});
Upvotes: 0
Reputation: 1510
uosɐſ's answer is fundamentally correct, but you probably don't want to do it in the mouseenter event. That's going to cause it to do the calculation to determine if it's needed, each time you mouse over the element. Unless the size of the element is changing, there's no reason to do that.
It would be better to just call this code immediately after the element is added to the DOM:
var $ele = $('#mightOverflow');
var ele = $ele.eq(0);
if (ele.offsetWidth < ele.scrollWidth)
$ele.attr('title', $ele.text());
Or, if you don't know when exactly it's added, then call that code after the page is finished loading.
if you have more than a single element that you need to do this with, then you can give them all the same class (such as "mightOverflow"), and use this code to update them all:
$('.mightOverflow').each(function() {
var $ele = $(this);
if (this.offsetWidth < this.scrollWidth)
$ele.attr('title', $ele.text());
});
Upvotes: 32
Reputation: 4394
If you want to do this solely using javascript, I would do the following. Give the span an id attribute (so that it can easily be retrieved from the DOM) and place all the content in an attribute named 'content':
<span id='myDataId' style='text-overflow: ellipsis; overflow : hidden;
white-space: nowrap; width: 71;' content='{$myData}'>${myData}</span>
Then, in your javascript, you can do the following after the element has been inserted into the DOM.
var elemInnerText, elemContent;
elemInnerText = document.getElementById("myDataId").innerText;
elemContent = document.getElementById("myDataId").getAttribute('content')
if(elemInnerText.length <= elemContent.length)
{
document.getElementById("myDataId").setAttribute('title', elemContent);
}
Of course, if you're using javascript to insert the span into the DOM, you could just keep the content in a variable before inserting it. This way you don't need a content attribute on the span.
There are more elegant solutions than this if you want to use jQuery.
Upvotes: 3