const attributeName = 'data-middle-ellipsis'
const clamp = (val,min=Number.NEGATIVE_INFINITY,max=Number.POSITIVE_INFINITY)=>Math.max(min,Math.min(max,val))
const ellipsis = '…'
const map = new Map()
let timeoutId
testMiddleEllipsis()
window.addEventListener('resize', onResize, { capture: true, passive: true })
function onResize(e){
clearTimeout(timeoutId)
timeoutId = setTimeout(testMiddleEllipsis, 200)
}
function testMiddleEllipsis() {
Array.from(document.querySelectorAll(`[${attributeName}]`)).forEach(elm=>{
const mapped = map.get(elm)
let {text, textLength, from, multiplier, font, textWidth, elementWidth} = mapped||{}
if (!mapped) {
text = elm.textContent
textLength = text.length
from = parseFloat(elm.getAttribute(attributeName))||50
multiplier = from>0&&from/100
const computedStyle = window.getComputedStyle(elm, null)
font = `${computedStyle.getPropertyValue('font-weight')} ${computedStyle.getPropertyValue('font-size')} ${computedStyle.getPropertyValue('font-family')}`
textWidth = getTextWidth(text, font)
elementWidth = elm.offsetWidth
map.set(elm, {text, textLength, from, multiplier, font, textWidth, elementWidth})
}
const {offsetWidth} = elm
const widthChanged = !mapped||elementWidth!==offsetWidth
mapped&&widthChanged&&(mapped.elementWidth=elementWidth=offsetWidth)
if (widthChanged&&textWidth>elementWidth) {
elm.setAttribute('title', text)
let smallerText = text
let smallerWidth = elementWidth
while(smallerText.length>3){
let smallerTextLength = smallerText.length
const half = multiplier&&
clamp(multiplier*smallerTextLength<<0,1,smallerTextLength-2)||
Math.max(smallerTextLength+from-1,1)
const half1 = smallerText.substr(0,half).replace(/\s*$/,'')
const half2 = smallerText.substr(half+1).replace(/^\s*/,'')
smallerText = half1+half2
smallerWidth = getTextWidth(smallerText+ellipsis, font)
if (smallerWidth<elementWidth) {
elm.textContent = half1+ellipsis+half2
break;
}
}
}
})
}
function getTextWidth(text, font) {
let context = getTextWidth.context
if (!context) {
const canvas = document.createElement('canvas')
context = getTextWidth.context = canvas.getContext('2d')
}
context.font = font
const metrics = context.measureText(text)
return metrics.width
}
html,body{
min-width: 100%;
min-height: 100%;
line-height: 170%;
}
main {
width: 100%;
height: 100%;
font-family: Arial;
background: #fff linear-gradient(90deg
,transparent 130px
,#f0f0f0 130px
,#f0f0f0 260px
,transparent 260px
);
}
[data-middle-ellipsis]{
max-width: 130px;
white-space: nowrap;
box-shadow: -1px 0 0 red inset;
}
.inline-block { display: inline-block; }
.bold { font-weight: bold; }
.small { font-size: 10px; }
.large { font-size: 32px; }
<main>
<h1 data-middle-ellipsis>A Javascript solution to middle ellipsis</h1>
<div data-middle-ellipsis>The quick fox</div>
<div data-middle-ellipsis class="inline-block">The lazy dog</div>
<div data-middle-ellipsis>theQuickBrownFoxJumpsOverTheLazyDog</div>
<div data-middle-ellipsis class="bold">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis class="small">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis class="small bold">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis class="large">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis>The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="70">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="30">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="1">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="99">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="-3">The quick brown fox jumps over the lazy dog</div>
<div data-middle-ellipsis="-3">The quick brown dog</div>
<div data-middle-ellipsis="0">The quick brown dog</div>
<div data-middle-ellipsis="-1">The quick brown dog</div>
<div data-middle-ellipsis="-99">The quick brown dog</div>
</main>