text元素文本自动换行实现
更新于 阅读 2 次
svg中 *
这是本人实现的一个思维导图的在线demo,双击节点进入编辑态,多输入一些字符就能自动换行了。
先来看一个在线例子,可自行在线编辑查看效果。
下面直接看代码:
const chineseRegx = /[\u4E00-\u9FA5\uF900-\uFA2D]/ // 中文 export const createSVGElement = type => { return document.createElementNS('http://www.w3.org/2000/svg', type) } // 将字符串拆成单词数组 const sliptWords = text => { text = text.replace('↵↵', '\n') text = text.replace('↵', '\n') const resultWordsArr = [] // 1. 拆成句子 const sentenceArr = text.split('\n') for (let i = 0; i < sentenceArr.length; i++) { // 2. 根据空格拆分 const wordsArr = sentenceArr[i].split(' ') for (let j = 0; j < wordsArr.length; j++) { // 单词拆成单个字符 const charArr = wordsArr[j].split('') // 判断单词是否可在拆分,比如中英文混合 let word = '' for (let m = 0; m < charArr.length; m++) { if (chineseRegx.test(charArr[m])) { if (word !== '') { resultWordsArr.push(word) word = '' } resultWordsArr.push(charArr[m]) } else { word += charArr[m] } } if (word !== '') { resultWordsArr.push(word) } if (j !== wordsArr.length - 1) { // 添加空格 resultWordsArr.push(' ') } } if (i !== sentenceArr.length - 1) { // 添加换行 resultWordsArr.push('\n') } } return resultWordsArr } // 测量文本宽高 let measureTextContext = null const measureText = (text, options = {}) => { if (!measureTextContext) { measureTextContext = document.createElement('canvas').getContext('2d') } measureTextContext.save() if (options.font) { measureTextContext.font = options.font } const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = measureTextContext.measureText(text) measureTextContext.restore() return { width, height: actualBoundingBoxAscent + actualBoundingBoxDescent } } const joinFontStr = font => { const arr = [] if (font.style) { arr.push(font.style) } if (font.weight) { arr.push(font.weight) } if (font.size) { let size = `${font.size}px` if (font.leading) { size += `/${font.leading}` } arr.push(size) } if (font.family) { arr.push(font.family) } return arr.join(' ') } // 设置文本最大宽度 const TEXT_WRAPPER_WIDTH = 200 const createTextNode = (text, font) => { // 字符串拆分成单词 const wordsArr = sliptWords(text) // 根据单词进行渲染 const fontStr = joinFontStr(font) const linesArr = [] let lineWrods = [] for (let i = 0; i < wordsArr.length; i++) { if (wordsArr[i] === '\n') { // 换行 linesArr.push(lineWrods.join('')) if (i === wordsArr.length - 1) { lineWrods = [''] } else { lineWrods = [] } continue } lineWrods.push(wordsArr[i]) const { width } = measureText(lineWrods.join(''), { font: fontStr }) if (width === TEXT_WRAPPER_WIDTH) { linesArr.push(lineWrods.join('')) lineWrods = [] continue } if (width > TEXT_WRAPPER_WIDTH) { // 如果宽度超出 // 1. lineWrods如果存在多个单词,直接推出一个单词, if (lineWrods.length === 1) { let word = lineWrods.pop() while (true) { // 开始截取单词直到剩下的字符宽度小于最大宽度 if ( measureText(word, { font: fontStr }).width < TEXT_WRAPPER_WIDTH ) { lineWrods = [word] break } for (let j = 0; j < word.length; j++) { if ( measureText(word.substring(0, j + 1), { font: fontStr }).width > TEXT_WRAPPER_WIDTH ) { // 添加tspan linesArr.push(word.substring(0, j)) word = word.substring(j) break } } } } else { // 2. 对单词进行强制断行 lineWrods.pop() linesArr.push(lineWrods.join('')) i-- lineWrods = [] continue } } } if (lineWrods.length) { linesArr.push(lineWrods.join('')) } // 添加textNode const g = createSVGElement('g') const textNode = createSVGElement('text') g.appendChild(textNode) linesArr.forEach(line => { const tspanNode = createSVGElement('tspan') tspanNode.textContent = line tspanNode.setAttribute('x', 0) tspanNode.setAttribute('dy', font.size * font.leading) textNode.appendChild(tspanNode) }) return g } // const textGroup = createTextNode('123121asdfasfasfasf现在测试text自动换行sfasfasfasfassdsdfasf', { style: 'normal', family: 'Open Sans', size: 16, leading: 1.5, weight: 400 }) const svg = document.getElementById('svg') svg.appendChild(textGroup)
原理:
- 字符串拆分:先拆成段落,然后根据空格拆成单词,再对单词进行处理判断是否包含汉字,如果包含就把汉字拆分出来,
- 循环拆分出来的单词数组,每次读入一个单词到lineWrods(lineWrods表示每一行要展示的单词),如果是换行符**\n**,就认为已经读取了一个完整的段落,lineWrods中的单词显示在一行中,lineWrods清空以备为新的行读入单词;如果不是就判断加入该新单词后该行的宽度是否超过最大宽度;如果没有超过就继续读入单词,否则该单词不加入,lineWrods中的单词显示在一行中,清空lineWrods中的数据继续读入数据
- 所有行的数据都读取完后就循环生成tspan元素并加入到text元素中。