How to Create a Speech Bubble in SVG Using SnapSVG

In the process of developing a chat program, I have animated figures moving across the screen engaging in conversations. One crucial aspect I am yet to implement is creating scalable speech bubbles for when users interact.

Being relatively new to SVG and working on my first serious game project, I decided to utilize CSS to ensure proper scaling. Here is the CSS code snippet I came up with:

.bubble {
    background-color: #eee;
    border: 2px solid #333;
    border-radius: 5px;
    color: #333;
    display: inline-block;
    font: 16px/24px sans-serif;
    padding: 12px 24px;
    position: relative;
}
.bubble:after,
.bubble:before {
    border-left: 20px solid transparent;
    border-right: 20px solid transparent;
    border-top: 20px solid #eee;
    bottom: -20px;
    content: '';
    left: 50%;
    margin-left: -20px;
    position: absolute;
}

/* Additional styling for second triangle (border) */

.bubble:before {
    border-left: 23px solid transparent;
    border-right: 23px solid transparent;
    border-top: 23px solid;
    border-top-color: inherit; /* Individual property needed */
    bottom: -23px;
    margin-left: -23px;
}

Unfortunately, this approach did not yield the desired results due to SVG's limitations in supporting certain CSS properties. At this point, I am unsure how to proceed. Can anyone provide guidance on how to create resizable speech bubbles in SVG?

Here is an example of an SVG path that I tried creating:

I successfully generated a small SVG path, but I am uncertain about enlarging it and filling it with text:

var mesasgeBox = chatSvg.path("M 200.444444444444446,200v-6h10.444444444444446v6h-4l-3.1111111111111107,1.6222222222222236l0.11111111111111072,-1.6222222222222236Z");

Answer №1

The following source code requires specific coordinates (x/y) for positioning and a maximum width for text wrapping. It is designed as a plugin for easy use. While not yet optimized, performance improvements can be made by caching the letter width based on font size.
The text wrapping functionality in this code is inspired by a solution found here How to either determine SVG text box width, or force line breaks after 'x' characters?

To customize the appearance of the bubble layout, simply replace the "paper.rect" element within the plugin.

Snap.plugin(function (Snap, Element, Paper, glob) {
     Paper.prototype.bubbletext = function (x, y, txt, maxWidth, attributes) {

        var svg = Snap();
        var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
        var preText = svg.text(0, 0, abc);
        preText.attr(attributes);
        var letterWidth = (preText.getBBox().width / abc.length);
        svg.remove();

        var words = txt.split(" ");
        var widthCalc = 0, activeLine = 0, lines=[''];
        for (var letterCount = 0; letterCount < words.length; letterCount++) {

           var l = words[letterCount].length;
           if (widthCalc + (l * letterWidth) > maxWidth) {
              lines.push('');
              activeLine++;
              widthCalc = 0;
           }
           widthCalc += l * letterWidth;
           lines[activeLine] += words[letterCount] + " ";
        }

        var padding = 10;

        var t = this.text(x+padding, y+15+padding, lines).attr(attributes);

        t.selectAll("tspan:nth-child(n+2)").attr({
           dy: "1.2em",
           x: x+padding
        });

        var boxHeight = t.node.clientHeight + (padding * 3);
        var messageBox = this.path("M " + (x-padding) + "," + (y-padding+boxHeight) + "v-" + boxHeight + "h" +  (t.node.clientWidth + (padding*3)) + "v"+boxHeight+"h-6l-9,15l0,-15Z");
        messageBox.attr({
            fill:"rgba(0, 0, 255, .3)"
        });
        t.before(messageBox);
        return t;
     };
  });

var div = document.querySelector('div.wrap');
var bubble = Snap('100%','100%').attr({ viewBox: '0  0 200 200' });;
bubble.bubbletext(0, 0, "Hallo Mike how are you. These text is auto wraped and the bubble size automaticaly. The svg result is also scaleable. Please change this text to test.", 155,
    { "font-size": "15px", "fill": "#000"});
div.appendChild(bubble.node);

CODEPEN

UPDATE

Add your custom bubble layout to the codepen example provided above.

UPDATE 2
An updated version of the source example has been implemented.

Answer №2

While there isn't a specific method to fill with text, you can manually place it and add animations.

By following these steps, you can create a bubble that animates along with the text.

For scaling transformations, use the format 'sX,Y,CX,CY'. The CX/CY values represent the center point for scaling. Snap will adjust automatically to scale around the center point, unlike traditional svg scale(x,y).

For example, 's20,20' would scale the element by 20 in both x and y directions.

var b = s.path("M 200.444444444444446,200v-6h10.444444444444446v6h-4l-3.1111111111111107,1.6222222222222236l0.11111111111111072,-1.6222222222222236Z").attr({ fill: 'gray' });

b.animate({ transform: 's10,10' }, 2000)

var t = s.text(190,200,'stuff!').attr({ stroke: 'yellow', fill: 'yellow', transform: 's0.2,0.2'})
t.animate({ transform: 's2,2'}, 2000)

jsfiddle

You may need to adjust the code to achieve your desired appearance and alignment.

Answer №3

An SVG graphic is embedded within a div element, and jQuery is utilized to create animations for size and position. A callback function is employed to make certain words visible, with the second line using the .fadeIn() method.

I trust this information proves beneficial.

$('div').append('<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0.00000000" y="0.00000000" width="100%" height="100%" id="svg3" viewbox="0 0 1000 600" >  <path fill="#FFFFFF" stroke="#000000" stroke-width="10" d="M916.902,397.331c14.126,0,17.344-9.739,17.344-9.739   c0-27.635,7.992-42.357,26.927-42.357c0,0,13.702,1.668,13.702-14.946c0-0.001,0.619-43.408-1.901-141.244   c-2.514-97.836-9.537-109.333-9.537-109.333c0-14.125-9.129-13.284-9.129-13.284c-24.67,0-53.406,4.151-53.406-30.893   c0,0,1.558-11.866-15.041-11.866c0,0-159.78-14.301-423.823-14.301c-264.041,0-375.12,2.352-375.12,2.352   c-14.125,0-13.284,9.136-13.284,9.136c0,22.479-13.575,42.622-30.319,42.622c0,0-13.705,0.341-13.705,16.949   c0,0-4.551,60.914-4.551,117.724c0,56.808,4.551,126.899,4.551,126.899c0,14.125,9.127,13.28,9.127,13.28   c24.9,0,29.944,10.568,29.944,30.322c0,0,1.038,15.744,25.709,15.744l248.677,5.155c0,0,46.81,25.855,64.76,39.665   c17.952,13.808,27.714,26.235,12.526,41.426c-6.669,6.666-11.966,12.474-9.571,21.187c2.277,8.256,26.797,22.168,29.903,23.746   c0.261,0.127,61.957,30.323,84.796,41.37c16.646,8.047,33.288,16.074,49.292,25.362c2.152,1.253,14.271,9.614,16.804,7.089   c2.484-2.479-11.174-12.959-12.823-14.315c-9.084-7.442-16.206-16.462-24.158-25.027c-12.481-13.465-25.133-26.788-37.746-40.133   c-7.044-7.464-13.884-15.167-21.144-22.43c-1.791-1.79-1.476-4.571,0.699-7.001c7.682-8.531,25.246-28.013,27.384-30.14   c2.739-2.731-1.814-7.121-1.814-7.121l-62.959-51.678L916.902,397.331z"/> <text x="200" y="200" font-size="72" color="blue" id="myText" style="display: none;" >Hello Stackoverflow</text> <text x="200" y="300" font-size="72" color="blue" id="myText2" style="display: none;" >Delayed text</text> </svg>');

$('div').draggable({
    handle: 'rect'
});

$('div').animate({ // shrink it
    width: "100px",
    height: "60px",
    top: "240px",
    left: "220px"
}, 0) 
.animate({ // animate to full size
    width: "500px",
    height: "300px",
    top: "0px",
    left: "0px"
}, 2000, function() { // show text
    // Animation complete.

     $('#myText').show();
     $('#myText2').fadeIn('slow');
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<div style="width:500px; height:300px; ">
</div>

Answer №4

Without utilizing SnapSVG, an alternative method would be: Generating SVG elements (created in Illustrator):

    <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 200 115.9" enable-background="new 0 0 150 115.9" xml:space="preserve">
<g>
    <g>
        <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="33.5436" y1="98.5111" x2="92.6585" y2="11.8584">
            <stop  offset="0" style="stop-color:#FFFFFF"/>
            <stop  offset="1" style="stop-color:#D1D3D4"/>
        </linearGradient>
        <path fill="url(#SVGID_1_)" stroke="#000000" stroke-miterlimit="10" d="M138.5,14.8c-7.8-2.2-33.8-3.8-64.7-3.8
            c-31.2,0-57.4,1.6-64.9,3.8c-0.4,0.1-0.8,0.2-1.1,0.4c-0.3,0.1-0.6,0.3-0.7,0.4c-1.5,1-2.4,2.7-2.4,4.6L14,80.4
            c0.3,2.6,1.6,4.4,3.4,5.1c1.7,0.8,5.9,1.6,11.8,2.2l9.7,0.8c6.6,0.4,14.4,0.7,22.8,0.8c-1.2,6.9,0.4,9.1-4,13.3
            c-8.7,8.3-14.1,7.7-14.1,7.7c1.4,0.5,11,1.7,20-4.8c6.9-4.9,6.1-8.4,7.7-16.2c0,0,0,0,0,0c28.4,0,51.8-1.9,54.5-4.3
            c1.4-0.9,2.4-2.6,2.8-4.7l14.6-60.2C143.1,17.5,141.1,15.3,138.5,14.8z"/>
        <g>
            <path fill="#E2E2E2" d="M138.5,14.8c-7.8-2.2-33.8-3.8-64.7-3.8c-31.2,0-57.4,1.6-64.9,3.8c-0.4,0.1-0.8,0.2-1.1,0.4
                c-0.3,0.1-0.6,0.3-0.7,0.4c-0.4,0.2-0.7,0.5-1,0.8c0.1,0,0.2-0.1,0.2-0.1C6.6,16.2,7,16.1,7.4,16c7.5-2.2,33.7-3.8,64.9-3.8
                c30.9,0,56.9,1.6,64.7,3.8c2.6,0.5,4.6,2.7,4.6,5.4L127,81.6c-0.3,1.6-0.9,3-1.8,3.9c0.2-0.1,0.4-0.2,0.5-0.3
                c1.4-0.9,2.4-2.6,2.8-4.7l14.6-60.2C143.1,17.5,141.1,15.3,138.5,14.8z"/>
        </g>
    </g>
</g>
<text id="bubbleText" transform="matrix(1 0 0 1 21.6668 57.9542)" font-family="'MyriadPro-Regular'" font-size="15.3912">POW! Shazaam. </text>
</svg>

Modify the text content using JavaScript

document.getElementById('bubbleText').textContent = "new text";

Adjust the size of the SVG using the 'viewBox' attribute on the SVG root element

JSFiddle

Answer №5

By setting the svg height and width to 100%, the svg element will adjust responsively to its container. Additionally, by using the viewbox, you can control the view of the elements inside it;

This means that the svg will adapt to any scaling applied to its container.

Checkout this pen for more info!

Answer №6

If your design is not primarily focused on SVG, it's best to avoid using it for speech bubbles. The positioning, scaling, and text issues can make working with SVG cumbersome. While SVG offers scalability and manipulation in the DOM, it may not be the most convenient option for speech bubbles.

Instead, consider creating speech bubbles using pure CSS like demonstrated here. Alternatively, you could pre-render a collection of speech bubble graphics and adjust them as needed before adding text on top. CSS is great for simple pop-ups, while pre-rendered graphics are more suitable for static speech bubbles without complex animations or additional elements.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible to manipulate CSS on a webpage using javascript?

I have incorporated this piece of JavaScript to integrate a chat box onto my website: window.HFCHAT_CONFIG = { EMBED_TOKEN: "XXXXX", ACCESS_TOKEN: "XXXXX", HOST_URL: "https://happyfoxchat.com", ASSETS_URL: "https://XXXXX.cloudfront.ne ...

Unable to attach the script to recently added DOM elements

After spending considerable time working on this, I'm still unable to figure it out. You can find the page I am referring to at: The "show more" button at the bottom triggers additional posts to be displayed on the page using the following script: ...

Ways to calculate a cumulative total on a web form using javascript

Below is the structure of a form: <form id="calculator" action="#" method="get"> <h1>Cake Cost Estimator</h1> <h3>Round Cakes:</h3> <fieldset id="round"> <table> <tr> ...

Turn off the scroll function while loading a webpage

Here is the script for my preloader: $(window).load(function() { $("#loading").fadeOut(1000); I want to prevent scrolling while the "loading" is still visible, and then enable it again after the fade out completes. ...

How can you resolve redefinition warnings detected by the CSS Validator?

After running my site through the W3C CSS Validator, I was surprised to see that it returned a total of 3146 warnings. The majority of these warnings are redefinition alerts, such as: .fa.fa-check Redefinition of color .fa.fa-check Redefinition of ...

Can someone guide me on the process of adding a personalized emoji to my discord bot?

After creating my own discord bot, I'm ready to take the next step and add custom emojis. While tutorials have helped me understand how to use client.cache to type an emoji, I'm unsure of how to upload them and obtain their ID for use in my bot. ...

The scatterplot dots in d3 do not appear to be displaying

My experience with d3 is limited, and I mostly work with Javascript and jQuery sporadically. I am attempting to build a basic scatterplot with a slider in d3 using jQuery. The goal of the slider is to choose the dataset for plotting. I have a JSON object ...

generate a dynamic dropdown menu using ajax to handle a vast amount of information

I have been tackling a challenge involving codeigniter and ajax. Specifically, I am working with two select boxes - one for countries and another for states. The goal is to dynamically populate the states select box based on the selected country using an a ...

How can I display only the y-axis values and hide the default y-axis line in react-chartjs-2?

Although I have some experience with chartjs, I am struggling to figure out how to hide the default line. To clarify, I have attached an image that illustrates the issue. I would like to achieve a result similar to this example: . My goal is to make it loo ...

AngularJS causing issues with Materializecss dropdown menu operation

I'm currently working on a web application using Materializecss and AngularJS for the front-end. However, I've encountered an issue with the dropdown menu component of Materialize not functioning as expected. Here's an example of the dropdo ...

Arrange Raphael Objects in Their Relative Positions

I've been experimenting with Raphael.js recently and I've encountered an issue related to the positioning of each Raphael object. My goal is to create multiple 'canvases' without having them overlap within a predefined div on the page. ...

Arrange the labels and input fields within nested elements in a synchronized manner

My data is structured semantically as shown below: <div class="inputs"> <div class="top"> <h4>Top</h4> <label for="top-1">Label 1</label> <input id="top- ...

Monitoring the content of a page with jQuery and adjusting the size as needed

Here is a code snippet: function adjustContainerHeight() { $('div#mainContainer').css({ 'min-height': $(document).height() - 104 // -104 compensates for a fixed header }).removeShadow().dropShadow({ 'blur&a ...

Is there a way to efficiently update specific child components when receiving data from websockets, without having to update each child individually?

Currently, my frontend requires updated data every 2 seconds. The process involves the frontend sending an init message to the backend over a websocket. Upon receiving this message, the backend initiates an interval to send the required data every 2 second ...

AngularJS error: Uncaught MinError Object

Recently, I started a new AngularJS project and successfully set it up. The installation of angular and angular-resource using bower went smoothly. However, upon installing another service that I have used previously - https://github.com/Fundoo-Solutions/a ...

Vue.js will trigger the updated() method only when a particular array undergoes changes

Working on a chat feature and looking for a way to automatically scroll to the end of the conversation when new messages are added. The current solution involving the updated() function works well, but there's a complication with a vue-timepicker com ...

Is there a way to create a JavaScript-driven search filter that updates automatically?

My website showcases a lineup of League of Legends champion icons, with one example shown below: <div class = "champion"> <p>Aatrox <img class = "face_left" src = "images/small/Aatrox.png"> <div class = "name" onmouseover="if(cha ...

Experiencing difficulties connecting with aspx while using Ext JS 4.2

Currently, I am facing an issue with making a call to an ASPX URL as the return keeps coming back as a failure. I have successfully used this URL in previous programming projects, but this is my first time utilizing it with Ext JS. Despite researching dif ...

Issues with Rock Paper Scissors Array in Discord.js V12 not functioning as expected

I'm currently working on implementing an RPS game in my Discord bot. I want to create a feature where if the choice made by the user doesn't match any of the options in the list, it will display an error message. Here is the code snippet that I h ...

Querying data with parameters in a client-side rendered application with Next.js

Currently, I am developing a chat application using Next.js and Axios. One of the functionalities I implemented is a dynamic route called chat/:pid to fetch data using the provided pid. However, I encountered an issue where the values are coming back as un ...