Javascript/Jquery 使文本完全适合给定尺寸的 div 并带有换行符(如果需要) [英] Javascript/Jquery making text to fit perfectly on div given dimensions with line break(if needed)

查看:15
本文介绍了Javascript/Jquery 使文本完全适合给定尺寸的 div 并带有换行符(如果需要)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很难解决这个问题:我使用这些函数将单行文本放入给定的尺寸中.

I'm having a little hard time working this out: I have these functions that I use in order to fit a single lined text into given dimensions.

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() 函数将文本绘制到画布上,尽可能合适,并根据其宽度和高度将其对齐在中心.我不确定这是否是达到预期结果的最有效方式,但它确实有效.

I pass the wanted dimensions and font to getFontSize function and it returns the real width and height of the text as well as the font size needed for it to be accomplished. This way I can draw the text to canvas using ctx.filltext() function, the best fit possible, and align it in the center based on its width and height. I'm not sure if it's the most efficient way of achieving the wanted result but it's working.

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

What I want to do now is instead of making it single lined, I can set a given width and height for a div and then, giving the text, it would fit perfectly those dimensions adding line breaks if needed, and still return the real width and height of the text so I can draw it correctly to the canvas like this fiddle: http://jsfiddle.net/vkgjrd3e/ although in the fiddle it still has some space on the bottom of the div.

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

What I'm trying to achieve is the best fit possible for the text, given its font and its container dimensions; adding line breaks if needed.

推荐答案

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

The easiest is probably to use the power of CSS instead of trying to do it yourself.

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

You can create a dummy div, append a <span> into it, and increase its font-size until it doesn't fit anymore.
The font-size before was the correct one.

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

Then, you can walk over this <span>'s textContent using a Range object in order to get the line-breaks CSS will have generated. The Range API provides a convenient getBoundingClientRect method, allowing us to find where the cursor is. We just have to remember the last y position and when it changes, we know the character before was the last of the line.

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

There are some downsides with this technique though.

  • 在 DOM 中,多个空格字符被压缩为一个.CanvasContext API 没有相同的行为,因此我们需要在解析文本之前摆脱这些.

  • In the DOM multiple space characters are compressed in a single one. CanvasContext API doesn't have the same behavior, so we need to get rid of these before parsing the text.

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

The measures are made based on the containers bounding boxes, this means it doesn't check for the painted pixels, so you may have some characters overflowing (for instance Zalgo͚̠͓ͣ̔͐̽) and some others not fitting perfectly in the box (depending on the ascent and descents of each characters).

...可能是其他人.

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>

这篇关于Javascript/Jquery 使文本完全适合给定尺寸的 div 并带有换行符(如果需要)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆