How can I load a custom svg in SVG JS

Viewed 68

I'm ripping apart a pong demo (https://css-tricks.com/pong-svg-js/) to learn about Javascript, SVGs, and SVG.js.

My version will draw a ball that bounces back and forth in the window. Using the "var ball = draw.circle(ballsize)" it works correctly, but when I try to substitute a custom SVG, it fails.

How can I correct this to draw my custom svg in place of the ball?

<html>
<body translate="no" >
<div id="svg-data" hidden>
<svg id="svg2" width="720" height="720" xmlns="http://www.w3.org/2000/svg">
 <g>
  <title>background</title>
  <rect fill="none" id="canvas_background" height="602" width="802" y="-1" x="-1"/>
 </g>
 <g>
  <title>Layer 1</title>
  <g id="layer1">
   <path d="m339.03,161.54001c166.28,1.498 323.57,64.413 335.55,203.73c8.98,101.9 -125.84,194.73 -292.11,194.73c-167.78,-1.5 -323.57,-62.91 -337.05,-202.18c-8.987,-103.45 127.24,-196.28 293.61,-196.28zm40.352,364.01c71.903,0 118.34,-68.954 110.85,-124.33c-16.478,-109.35 -53.928,-197.78 -152.79,-197.78c-71.903,0 -113.85,70.405 -104.86,125.83c16.571,109.4 48.029,196.28 146.8,196.28z" id="path270"/>
  </g>
 </g>
</svg>
</div>
  <div id="pong"></div>

<script src='https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.7.1/svg.min.js'></script>
  
<script id="rendered-js" >
// define document width and height
var width = 450,height = 300;

// create SVG document and set its size
var draw = SVG('pong').size(width, height);
draw.viewbox(0, 0, 450, 300);


// draw background
var background = draw.rect(width, height).fill('#dde3e1');


// define ball size
var ballSize = 10;

// original image - create ball
//var ball = draw.circle(ballSize);

// custom image - fails
var ball = draw.svg(document.getElementById('svg2').innerHTML);
ball.size(30,30);
ball.center(width / 2, height / 2).fill('#7f7f7f');

var ballDirection = -1;
var ballSpeed = 100;

function update(dt) {
    ball.dmove( ballDirection * ballSpeed * dt, 0);
    
    if (ball.cx() < 0 || ball.cx() > width) {
        if (ball.cx() < 0) { 
            ball.cx(0); 
        }
        else if (ball.cx() > width) { 
            ball.cx(width); 
        }
        ballDirection *= -1;
    }
}

var lastTime, animFrame;

function callback(ms) {
    cancelAnimationFrame(animFrame);
    
    if (lastTime) {
        update((ms - lastTime) / 1000);
    }
    
    lastTime = ms;
    animFrame = requestAnimationFrame(callback);
}

callback();

</script>
  
</body>
</html>
1 Answers

Let's say you have a file containing the grafics of a tennis ball. I have selected that file for its minimal size, the markup content of something you would like to use could be much larger:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
  <circle fill="#77B255" cx="18" cy="18" r="18"/>
  <path fill="#A6D388" d="M26 18c0 6.048 2.792 10.221 5.802 11.546C34.42 26.42 36 22.396 36 18c0-4.396-1.58-8.42-4.198-11.546C28.792 7.779 26 11.952 26 18z"/>
  <path fill="#FFF" d="M27 18c0-6.048 1.792-10.221 4.802-11.546-.445-.531-.926-1.028-1.428-1.504C27.406 6.605 25 10.578 25 18c0 7.421 2.406 11.395 5.374 13.05.502-.476.984-.973 1.428-1.504C28.792 28.221 27 24.048 27 18z"/>
  <path fill="#A6D388" d="M10 18c0-6.048-2.792-10.22-5.802-11.546C1.58 9.58 0 13.604 0 18c0 4.396 1.58 8.42 4.198 11.546C7.208 28.22 10 24.048 10 18z"/><path fill="#FFF" d="M4.198 6.454C7.208 7.78 9 11.952 9 18c0 6.048-1.792 10.22-4.802 11.546.445.531.926 1.027 1.428 1.504C8.593 29.395 11 25.421 11 18c0-7.421-2.406-11.395-5.374-13.049-.502.476-.984.972-1.428 1.503z"/>
</svg>

SVG.js would be able to reference that file in an SVG <image> tag:

var ball = draw.image('/path/to/Twemoji12 1f3be.svg').size(ballsize, ballsize)

But that has a drawback: the ball is loaded from a separate file, you need a request for that that takes time to fullfill (just like with every image file).

But the ball is just something you can write into your main file. That is where the size of your grafic markup will make a difference.

Variant 1: draw every tag separately

This is what SVG.js is really meant to do.

// draw a wrapper element, here you would use a nested `<svg>` element
var ball = draw.svg().size(ballsize, ballsize).viewbox(0 0 36 3)
// draw the circle
ball.circle(18).cx(18).cy(18).fill('#77B255')
// and so on for every tag and every attribute

Frankly: this is just more work than you probably would like to do.

Variant 2: construct the elements from strings

var ball = SVG(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
  <circle fill="#77B255" cx="18" cy="18" r="18"/>
  <path fill="#A6D388" d="M26 18c0 6.048 2.792 10.221 5.802 11.546C34.42 26.42 36 22.396 36 18c0-4.396-1.58-8.42-4.198-11.546C28.792 7.779 26 11.952 26 18z"/>
  <path fill="#FFF" d="M27 18c0-6.048 1.792-10.221 4.802-11.546-.445-.531-.926-1.028-1.428-1.504C27.406 6.605 25 10.578 25 18c0 7.421 2.406 11.395 5.374 13.05.502-.476.984-.973 1.428-1.504C28.792 28.221 27 24.048 27 18z"/>
  <path fill="#A6D388" d="M10 18c0-6.048-2.792-10.22-5.802-11.546C1.58 9.58 0 13.604 0 18c0 4.396 1.58 8.42 4.198 11.546C7.208 28.22 10 24.048 10 18z"/><path fill="#FFF" d="M4.198 6.454C7.208 7.78 9 11.952 9 18c0 6.048-1.792 10.22-4.802 11.546.445.531.926 1.027 1.428 1.504C8.593 29.395 11 25.421 11 18c0-7.421-2.406-11.395-5.374-13.049-.502.476-.984.972-1.428 1.503z"/>
</svg>`)
draw.add(ball).size(ballsize, ballsize)

A bit better, but at least I don't like mixing Javascript and markup inside the same file. I would go with a

Variant 3: bypass SVG.js and just write the markup in your HTML markup directly

I am saddling you with a high learning curve here, but ultimately you wanted to get that out of it, didn't you?

SVG contains its own templating mechanism, whith an element called <symbol>. Here is how your initial markup could look:

<div id="pong">
  <svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
    <symbol id="ball" viewBox="0 0 36 36">
      <circle fill="#77B255" cx="18" cy="18" r="18"/>
      <path fill="#A6D388" d="M26 18c0 6.048 2.792 10.221 5.802 11.546C34.42 26.42 36 22.396 36 18c0-4.396-1.58-8.42-4.198-11.546C28.792 7.779 26 11.952 26 18z"/>
      <path fill="#FFF" d="M27 18c0-6.048 1.792-10.221 4.802-11.546-.445-.531-.926-1.028-1.428-1.504C27.406 6.605 25 10.578 25 18c0 7.421 2.406 11.395 5.374 13.05.502-.476.984-.973 1.428-1.504C28.792 28.221 27 24.048 27 18z"/>
      <path fill="#A6D388" d="M10 18c0-6.048-2.792-10.22-5.802-11.546C1.58 9.58 0 13.604 0 18c0 4.396 1.58 8.42 4.198 11.546C7.208 28.22 10 24.048 10 18z"/><path fill="#FFF" d="M4.198 6.454C7.208 7.78 9 11.952 9 18c0 6.048-1.792 10.22-4.802 11.546.445.531.926 1.027 1.428 1.504C8.593 29.395 11 25.421 11 18c0-7.421-2.406-11.395-5.374-13.049-.502.476-.984.972-1.428 1.503z"/>
    </symbol>
  </svg>
</div>

What did I do here?

  • I copied the file content inside your wrapper <div>.
  • Then I inserted a <symbol> element so that it wraps the grafical elements.
  • The <svg> element gets zero size, because it initially only contains the template (I'm simplifying.)
  • The viewBox attribute gets moved to the <symbol> and a unique id is added.

Now, your script can work just as before. The line

var draw = SVG('pong').size(width, height)

would just insert a second <svg> element after that first, invisible one. But you could also pickup the first one and draw there. The initial zero size gets overwritten.

var draw = SVG('#pong svg').size(width, height)

In both variants, the ball is then used in the form of a reference:

var ball = draw.use('ball').size(ballsize, ballsize)
Related