简体   繁体   中英

Add responsive speechbubble to canvas using javascript

I'm currently drawing a speechbubble on a canvas element using javascript .

Explaination:

  • var text contains the text which will be added to the speechbubble.
  • Now getTextHeight function will get the height of the text stored in var text .
  • The component function will be called to add the speechbubble to the canvas element depending on the height and width of the text.
  • wrapText function will be called inside the component function to add a line break to the text depending on the passed maxwidth value. For example I pass the value 200 now the textline would be broken after 200 pixels and add the remaining text to the next textline.

My problem:

The speechbubble width and height won't fit the final text (line-broken) since I break the text after I delivered the width and height of the non-text-line broken text.

My question:

How could I get the width and height of the line broken text to pass it to the component function, so the speechbubble will fit the line-broken text?

Desired outcome:

The speechbubble should have the width and height of the line broken text.

Image to show the desired outcome

The code:

 var canvas = document.getElementById('canvas'); var ctx = canvas.getContext("2d"); ctx.font = "12px Helvetica"; var text = 'Speechbubble Speechbubble Speechbubble Speechbubble Speechbubble Speechbubble Speechbubble'; var fontHeight = getTextHeight(text, ctx.font); component(ctx, ctx.measureText(text).width, fontHeight, "#444", 10, 10, 5, text); function component(ctx, width, height, color, x, y, radius, text) { var r = radius; var w = width + 40; var h = height + 40; var pi2 = Math.PI * 2; var ap = w - 32; var aw = 20; var ah = 10; // Speechbubble create start ctx.beginPath(); ctx.arc(r, r, r, pi2 * 0.5, pi2 * 0.75); ctx.arc(w - r, r, r, pi2 * 0.75, pi2); ctx.arc(w - r, h - r, r, 0, pi2 * 0.25); ctx.lineTo(w - ap, h); ctx.lineTo(w - ap - (aw / 2), h + ah); ctx.lineTo(w - ap - aw, h); ctx.arc(r, h - r, r, pi2 * 0.25, pi2 * 0.5); ctx.fillStyle = color; ctx.fill(); // Speechbubble create end // Speechbubble text start ctx.fillStyle = "#fff"; wrapText(ctx, text, w - ctx.measureText(text).width - 25, h - fontHeight - 17, 300, 16); // Speechbubble text end } // Function to wrap the text depending on the maxWidth value // Function also adds the text to the following location: x, y function wrapText(context, text, x, y, maxWidth, lineHeight) { var words = text.split(' '); var line = ''; for(var n = 0; n < words.length; n++) { var testLine = line + words[n] + ' '; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > maxWidth && n > 0) { context.fillText(line, x, y); line = words[n] + ' '; y += lineHeight; } else { line = testLine; } } context.fillText(line, x, y); } // Function to get the height of the text function getTextHeight(txt, font) { var el = document.createElement('div'), height; el.style.cssText = "position:fixed; padding:0; left:-9999px; top:-9999px; font:" + font; el.textContent = txt; document.body.appendChild(el); height = parseInt(getComputedStyle(el).getPropertyValue('height'), 10); document.body.removeChild(el); return height; } 
 <canvas width="1200" height="1200" id="canvas"></canvas> 

You will have to add some logic and math...

var maxWidth = 500;

var w;
if (width > maxWidth) {
    w = maxWidth;
} else {
    w = width;
}
w += 40;

var h;
if (width > maxWidth) {
    var numberOfRows = Math.ceil(width / maxWidth);
    h = numberOfRows * height;
} else {
    h = 16;
}
h += 40;

(The example below uses a minified version of the above but does the exact same thing)

After that you have to re-adjust the coordinates for the text.
From:

wrapText(ctx, text, w - ctx.measureText(text).width - 25, h - fontHeight - 17, 300, 16);

To:

wrapText(ctx, text, 25, 17, maxWidth, 16);

Example:

 var canvas = document.getElementById('canvas'); var ctx = canvas.getContext("2d"); ctx.font = "12px Helvetica"; var testCases = [ "Lorem ipsum dolor sit amet", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.", "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." ], testCaseIndex = 0; setInterval(function() { var text = testCases[testCaseIndex]; ctx.clearRect(0, 0, canvas.width, canvas.height); var fontHeight = getTextHeight(text, ctx.font); component(ctx, ctx.measureText(text).width, fontHeight, "#444", 10, 10, 5, text); testCaseIndex = (testCaseIndex + 1) % testCases.length; }, 2000) function component(ctx, width, height, color, x, y, radius, text) { var maxWidth = 300; var r = radius; var w = (width > maxWidth ? maxWidth : width) + 40; var h = ((width > maxWidth ? Math.ceil(width / maxWidth) : 1) * height) + 40; var pi2 = Math.PI * 2; var ap = w - 32; var aw = 20; var ah = 10; // Speechbubble create start ctx.beginPath(); ctx.arc(r, r, r, pi2 * 0.5, pi2 * 0.75); ctx.arc(w - r, r, r, pi2 * 0.75, pi2); ctx.arc(w - r, h - r, r, 0, pi2 * 0.25); ctx.lineTo(w - ap, h); ctx.lineTo(w - ap - (aw / 2), h + ah); ctx.lineTo(w - ap - aw, h); ctx.arc(r, h - r, r, pi2 * 0.25, pi2 * 0.5); ctx.fillStyle = color; ctx.fill(); // Speechbubble create end // Speechbubble text start ctx.fillStyle = "#fff"; wrapText(ctx, text, 25, 17, maxWidth, 16); // Speechbubble text end } // Function to wrap the text depending on the maxWidth value // Function also adds the text to the following location: x, y function wrapText(context, text, x, y, maxWidth, lineHeight) { var words = text.split(' '); var line = ''; for(var n = 0; n < words.length; n++) { var testLine = line + words[n] + ' '; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > maxWidth && n > 0) { context.fillText(line, x, y); line = words[n] + ' '; y += lineHeight; } else { line = testLine; } } context.fillText(line, x, y); } // Function to get the height of the text function getTextHeight(txt, font) { var el = document.createElement('div'), height; el.style.cssText = "position:fixed; padding:0; left:-9999px; top:-9999px; font:" + font; el.textContent = txt; document.body.appendChild(el); height = parseInt(getComputedStyle(el).getPropertyValue('height'), 10); document.body.removeChild(el); return height; } 
 <canvas width="1200" height="1200" id="canvas"></canvas> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM