Javascript/Jquery 使文本完美地适合 div 给定尺寸和换行符(如果需要)

Posted

技术标签:

【中文标题】Javascript/Jquery 使文本完美地适合 div 给定尺寸和换行符(如果需要)【英文标题】:Javascript/Jquery making text to fit perfectly on div given dimensions with line break(if needed) 【发布时间】:2019-06-09 18:09:48 【问题描述】:

我很难解决这个问题:我使用这些函数来将单行文本适应给定的尺寸。

function getFontSize(width, height, text, font, callback) 
    var n = 100;
    var ctxfont = n + 'px ' + font;
    var result = measureTextHeight(ctxfont, text);
    while (result.width > width || result.height > height) 
        n--;
        var ctxfont = n + 'px ' + font;
        var result = measureTextHeight(ctxfont, text);
    
    callback(
        'width': result.width,
        'height': result.height,
        'size': n
    );


function measureTextHeight(ctxFont, text) 
    var width = 1500;
    var height = 500;

    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    var ctx = canvas.getContext("2d");
    ctx.save();
    ctx.font = ctxFont;
    ctx.clearRect(0, 0, width, height);
    ctx.fillText(text, parseInt(width * 0.1, 10), parseInt(height / 2, 10));
    ctx.restore();
    document.body.appendChild(canvas);
    var data = ctx.getImageData(0, 0, width, height).data;


    var topMost = false;
    var bottomMost = false;
    var leftMost = false;
    var rightMost = false;
    for (var x = 0; x < width; x++) 
        for (var y = 0; (y < height) && (!leftMost); y++) 
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) 
                leftMost = x;
            
        
    
    for (var y = 0; y < height; y++) 
        for (var x = 0; (x < width) && (!topMost); x++) 
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) 
                topMost = y;
            
        
    
    for (var x = width - 1; x >= 0; x--) 
        for (var y = height - 1; (y >= 0) && (!rightMost); y--) 
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) 
                rightMost = x;
            
        
    
    for (var y = height - 1; y >= 0; y--) 
        for (var x = width - 1; (x >= 0) && (!bottomMost); x--) 
            //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
            if (data[getAlphaIndexForCoordinates(x, y, width, height)] != 0) 
                bottomMost = y;
            
        
    
    canvas.remove();
    return (
        width: (rightMost - leftMost) + 1
        , height: (bottomMost - topMost) + 1
    );


function getAlphaIndexForCoordinates(x, y, width, height) 
    return (((width * 4 * y) + 4 * x) + 3);

我将所需的尺寸和字体传递给 getFontSize 函数,它会返回文本的实际宽度和高度以及完成它所需的字体大小。这样,我可以使用 ctx.filltext() 函数将文本绘制到画布上,这是最合适的,并根据其宽度和高度将其对齐在中心。我不确定这是否是实现预期结果的最有效方式,但它正在发挥作用。

我现在想要做的不是让它单行,我可以为 div 设置给定的宽度和高度,然后给出文本,它会完全适合那些尺寸,如果需要添加换行符,并且仍然返回文本的实际宽度和高度,因此我可以像这样将它正确绘制到画布上:http://jsfiddle.net/vkgjrd3e/ 尽管在小提琴中它仍然在 div 的底部有一些空间。

考虑到它的字体和容器尺寸,我想要实现的是最适合文本;如果需要,添加换行符。

【问题讨论】:

你的主要问题是什么?? @Sushil 我正在寻找的是当我将尺寸和文本传递给函数时,它太长了,如果需要,它会自动换行。假设我向函数传递了一个大文本,宽度和高度为 200px。我希望它返回正确的字体大小和带有换行符的文本(如果需要),以便它很好地适应度量。我的代码已经这样做了,但它没有添加换行符,这就是我坚持的地方。如果我需要在画布上添加长文本,它只会缩小它直到它适合一行。检查当前发生的事情:jsfiddle.net/pqhudmj0 @MarcielFonseca 你需要这样的东西吗? jsfiddle.net/bpyqfhaz @Sushil 正是如此。文本将从我的 API 中获取,然后将给定的宽度和高度传递给函数 【参考方案1】:

最简单的方法可能是使用 CSS 的强大功能,而不是尝试自己动手。

您可以创建一个虚拟 div,在其中附加一个 ,然后增加它的字体大小,直到它不再适合。 之前的字体大小是正确的。

然后,您可以使用 Range 对象遍历此 的 textContent,以获取 CSS 将生成的换行符。 Range API 提供了一个方便的getBoundingClientRect 方法,让我们可以找到光标所在的位置。我们只需要记住最后一个 y 位置,当它发生变化时,我们就知道之前的字符是该行的最后一个字符。

不过,这种技术也有一些缺点。

在 DOM 中,多个空格字符被压缩为一个。 CanvasContext API 没有相同的行为,所以我们需要在解析文本之前去掉这些。

测量是基于容器边界框进行的,这意味着它不会检查绘制的像素,所以你可能有一些字符溢出(例如 Zalgo͚̠͓ͣ̔͐̽)和其他一些不完全适合 在框中(取决于每个字符的上升和下降)。

...可能是其他人。

function getBestFontSize(text, width, height, userstyles) 
  if(!text) return null;
  
  const cont = document.createElement('div');
  cont.classList.add('best-font-size-tester');
  const style = cont.style;

  if(typeof width === 'number') width += 'px';
  if(typeof height === 'number') height += 'px';
  Object.assign(style, width, height, userstyles);

  const span = document.createElement('span');
  span.textContent = text;
  cont.appendChild(span);
  document.body.appendChild(cont);
  
  let size = 0;
  
  const max = cont.getBoundingClientRect();
  while(true) 
    style.fontSize = size + 'px';
    let rect = span.getBoundingClientRect();
    if(rect.bottom > max.bottom || rect.right > max.right) 
      // overflown
      size -= 1; // the correct size was the one before
      break;
    
    size++;
  
  if(size === 0) 
    // even at 0 it doesn't fit...
    return null;
  
  // now we'll get the line breaks by walking through our text content
  style.fontSize = size + 'px';
  const lines = getLineBreaks(span.childNodes[0], max.top);
  // cleanup
  document.body.removeChild(cont);

  return 
    fontSize: size,
    lines: lines
  ;


function getLineBreaks(node, contTop) 
  if(!node) return [];
  const range = document.createRange();
  const lines = [];
  range.setStart(node, 0);
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1;
  let lastFound = 0;
  let bottom = 0;
  while(current <= str.length) 
    range.setStart(node, current);
    if(current < str.length -1)
      range.setEnd(node, current+1);
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) 
      lines.push(
        y: prevBottom - (contTop || 0),
        text: str.substr(lastFound , current - lastFound)
      );
      prevBottom = bottom;
      lastFound = current;
    
    current++;
  
  // push the last line
  lines.push(
    y: bottom - (contTop || 0),
    text: str.substr(lastFound)
  );

  return lines;


const ctx = canvas.getContext('2d');
ctx.textBaseline = 'bottom';
txt_area.oninput = e => 
  const input = txt_area.value
    .replace(/(\s)(?=\1)/g, ''); // remove all double spaces
  ctx.setTransform(1,0,0,1,0,0);
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.translate(19.5,19.5);
  ctx.strokeRect(0,0,100,100);

  if(!input.length) return;
  const bestFit = getBestFontSize(input, 100, 100, 
    fontFamily: 'sans-serif',
    fontWeight: '600',
    textAlign: 'center'
  );
  // apply thesame options we passed
  ctx.font = '600 ' + bestFit.fontSize + 'px sans-serif';
  ctx.textAlign = 'center';
  // translate again because text-align: center
  ctx.translate(50.5,0);
  bestFit.lines.forEach((text, y) => ctx.fillText(text, 0, y));
;
txt_area.oninput();
.best-font-size-tester 
  border: 1px solid;
  position: absolute;
  overflow: visible;
  opacity: 0;
  z-index: -1;
  pointer-events: none;
<textarea id="txt_area">This is an example text to fit the div even with line breaks</textarea>
<canvas id="canvas"></canvas>

【讨论】:

这是一些很棒的东西。我一直在寻找什么。有没有办法避免用重音和垂直居中的小词溢出字符?在您的 sn-p 中,如果您键入 Á 和单个单词,例如“Testing”,您可以看到它们不会垂直居中,并且“Á”重音将开箱即用,这将使我的脚本错误地测量x 和 y 作为结果 @MarcielFonseca 对于垂直居中,您可以将一些样式规则传递给函数的 userstyles 参数,例如 display: 'flex', 'align-items': 'center' will do it 。对于重音字符,不幸的是,不,这是我谈到的缺点之一,边界框不关心绘制的内容,它们只看字体声明的上升和下降。 我明白了。无论如何,这就是我一直在寻找的,我接受答案,谢谢

以上是关于Javascript/Jquery 使文本完美地适合 div 给定尺寸和换行符(如果需要)的主要内容,如果未能解决你的问题,请参考以下文章

Typeahead 不适用于 javascript/jquery

JavaScript Jquery - 使div可点击

如何使用 jquery/javascript 将文本插入 json [重复]

当您使用 javascript 或 jquery 将鼠标悬停时,如何使图像或按钮发光?

JavaScript / jQuery:如何在 Firefox 中获取选定的文本

更改跨度文本值 JavaScript/jQuery