How to get height between top of div and top of current word font (inside this div)

Viewed 78

How can I get height between two red lines (top of div and top of current word font inside this div)?

man valley army

<div class="word">man</div>

.word {
   font-size: ...
   font-family: ...
}

I tried to create canvas and use measureText method:

context.measureText(text);

But I can't find useful info in that result...

textMetrics

do you have any ideas?

1 Answers

The TextMetrics object you received actually holds enough information to let us know the width and height of our text.
In most simple cases we can thus just combine this information with the ones of the parent element's bounding box to get the actual bounding box of single-line text.

function getWordBBox( element ) {
  // store the context as a property of the function
  if( !getWordBBox.ctx ) {
    const canvas = document.createElement( "canvas" );
    canvas.width = canvas.height = 0; // no need to use any memory
    getWordBBox.ctx = canvas.getContext( "2d" );
  }
  const ctx = getWordBBox.ctx;
  // grab computed styles from the element
  const {
    fontStyle, // copy font styles
    fontWeight,
    fontSize,
    fontFamily,
    paddingTop, // get inner offsets
    paddingLeft,
    borderTopWidth,
    borderLeftWidth
   } = getComputedStyle( element );
  const elemBBox = element.getBoundingClientRect();
  // set our context with these settings
  ctx.font = [ fontStyle, fontWeight, fontSize, fontFamily ].join( " " );
  ctx.textBaseline = "top";
  ctx.textAlign = "start";
  
  // grab our TextMetrics object
  const metrics = ctx.measureText( element.textContent );
  // the BBox
  const elem_inner_left = elemBBox.left +
    parseFloat( borderLeftWidth ) + parseFloat( paddingLeft );
  const elem_inner_top = elemBBox.top +
    parseFloat( borderTopWidth ) + parseFloat( paddingTop );
  return {
    left: metrics.actualBoundingBoxLeft * -1 + elem_inner_left,
    top: metrics.actualBoundingBoxAscent * -1 + elem_inner_top,
    width: metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft,
    height: metrics.actualBoundingBoxDescent + metrics.actualBoundingBoxAscent
  };
}

const elem = document.querySelector( ".word" );
elem.oninput = (evt) => {
  const bbox = getWordBBox( elem );
  // simply to check we got the bbox of the text correctly
  const box_elem = document.querySelector( ".box" );
  box_elem.style.setProperty( "top", Math.floor( bbox.top ) + "px" );
  box_elem.style.setProperty( "left", Math.floor( bbox.left ) + "px" );
  box_elem.style.setProperty( "width", Math.ceil( bbox.width ) + "px" );
  box_elem.style.setProperty( "height", Math.ceil( bbox.height ) + "px" );

  const elemBBox = elem.getBoundingClientRect();
  const distance_from_top = bbox.top - elemBBox.top;
  
  const dist_elem = document.querySelector( ".vertical-dist" );
  dist_elem.style.setProperty( "top", elemBBox.top  + "px" );
  dist_elem.style.setProperty( "left", elemBBox.left + "px" );
  dist_elem.style.setProperty( "width", elemBBox.width - 1 + "px" );
  dist_elem.style.setProperty( "height", distance_from_top + "px" );
  console.clear();
  console.log( "distance from top:", distance_from_top );
};
elem.oninput();
.word {
  font-size: 30px;
  font-family: sans-serif;
  border: 2px solid red;
  padding: 20px;
  margin: 50px;
  line-height: 1;
}
.box {
  background: green;
  position: absolute;
  pointer-events: none;
  opacity: 0.3;
}
.vertical-dist { 
  width: 100%;
  position: absolute;
  background-color: blue;
  opacity: 0.3;
  pointer-events: none;
}
<div class="word" contenteditable>man</div>
<div class="box"></div>
<div class="vertical-dist"></div>

But once again, this will work only with simplest cases, for instance it doesn't handle any line-height other than 1, it doesn't support either transformed elements and other shenanigans, and only treats the textContent as a single-line text.

Related