Middle ellipsis

by Ron Valstar

HTML

<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>

SCSS

$width: 130px;

html,body{
  width: 100%;
  height: 100%;
  line-height: 170%;
}

main {
  width: 100%;
  height: 100%;
  font-family: Arial;
  background: #fff linear-gradient(90deg
    ,transparent $width
    ,#f0f0f0 $width
    ,#f0f0f0 2*$width
    ,transparent 2*$width
  );
}

[data-middle-ellipsis]{
  max-width: $width;
  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; }

JavaScript

/**
 * Attibute modifier to create middle ellipsis
 * When the attribute value is left blank the ellipsis will be in the middle
 * When positive the attribute value will be used as a percentage
 * When negative the attribute value will be used as character index counted from the end
 * @example
 *   <div data-middle-ellipsis>A Javascript solution to middle ellipsis</div>
 *   <div data-middle-ellipsis="20">A Javascript solution to middle ellipsis</div>
 *   <div data-middle-ellipsis="-3">A Javascript solution to middle ellipsis</div>
 */
const attributeName = 'data-middle-ellipsis'
const attributeTextName = attributeName+'-text'
const attributeFontName = attributeName+'-font'
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=>{
    // do not recalculate variables a second time
    const mapped = map.get(elm)
    let {text, textLength, from, multiplier, font, textWidth, elementWidth} = mapped||{}
    // first time
    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 =...