Miki
Miki

Reputation: 469

Dynamically adding a SVG gradient

I have this SVG container with paths. I want to edit it, so the paths' fill will be a pattern. This is my failed attempt:

I add a gradient:

$('svg defs').prepend('<linearGradient id="MyGradient"><stop offset="5%" stop-color="#F60" /><stop offset="95%" stop-color="#FF6" /></linearGradient>');

And then change the paths' fill:

$(base + ' svg path').each(function() {
    this.setAttribute('fill','url(#MyGradient)')
}

This doesn't work. What am I missing?

Upvotes: 10

Views: 17944

Answers (4)

Hoffmann
Hoffmann

Reputation: 14729

I just want to drop by and say I have found a more elegant solution that allows you to keep using jQuery with SVG elements but without the jQuery SVG library (which is no longer being updated and has some problems with jQuery 1.8 or higher). Simply use a function like this:

createSVGElement= function(element) {
    return $(document.createElementNS('http://www.w3.org/2000/svg', element));
}

it creates a SVG element on the SVG namespace and encapsulates it with jQuery, once the element is created in the right namespace you can use it freely with jQuery:

You can then use the function in this manner:

var $myGradient= createSVGElement('linearGradient')
    .attr( {
        id:"MyGradient"
    });

//if you dont need `defs`, skip this next line
var $myDefs = createSVGElement('defs');

createSVGElement('stop')
    .attr({
        offset: "5%",
        "stop-color": "#F60"
    })
    .appendTo($myGradient);


createSVGElement('stop')
    .attr({
        offset:"95%",
        "stop-color":"#FF6"
    })
    .appendTo($myGradient);

//Use this if you already have `defs`
$('svg defs').prepend($myGradient);

//Use this if you dont have `defs`
$('svg').prepend($myDefs);
$('svg defs').prepend($myGradient);

It's not as compact as you might want it to be since you have to create each element by hand, but its a lot better than manipulating everything with DOM methods.

A small note, jQuery .attr() function assumes all attributes are lowercased, which is not the case for SVG elements (for example the viewBox attribute in <svg> tags). To get around that, when setting attributes with uppercased letters use something like this:

$("svg")[0].setAttribute("viewBox", "0 0 1000 1000");

Upvotes: 3

Phrogz
Phrogz

Reputation: 303301

Your problem (what you are "missing") is that jQuery creates new elements in the XHTML namespace, while SVG elements must be created in the SVG namespace. You cannot use raw code in a string for SVG elements.

The simplest (no-plugins) method is to stop leaning on jQuery so much and just use simple DOM methods to create the elements. Yes, it's more verbose than just using jQuery to magically construct your elements for you...but jQuery does not work in this case.

Demo: http://jsfiddle.net/nra29/2/

createGradient($('svg')[0],'MyGradient',[
  {offset:'5%', 'stop-color':'#f60'},
  {offset:'95%','stop-color':'#ff6'}
]);
$('svg path').attr('fill','url(#MyGradient)');

// svg:   the owning <svg> element
// id:    an id="..." attribute for the gradient
// stops: an array of objects with <stop> attributes
function createGradient(svg,id,stops){
  var svgNS = svg.namespaceURI;
  var grad  = document.createElementNS(svgNS,'linearGradient');
  grad.setAttribute('id',id);
  for (var i=0;i<stops.length;i++){
    var attrs = stops[i];
    var stop = document.createElementNS(svgNS,'stop');
    for (var attr in attrs){
      if (attrs.hasOwnProperty(attr)) stop.setAttribute(attr,attrs[attr]);
    }
    grad.appendChild(stop);
  }

  var defs = svg.querySelector('defs') ||
      svg.insertBefore( document.createElementNS(svgNS,'defs'), svg.firstChild);
  return defs.appendChild(grad);
}

Using a Library

Alternatively, you can include Keith Woods' "jQuery SVG" plugin that has a lot of convenience methods for common SVG operations, including the ability to create linear gradients.

Upvotes: 24

Sirko
Sirko

Reputation: 74076

I think you'll have to use the SVG plugin for jQuery (found here). When adding SVG elements using the "normal" jQuery library, probably the namespaces get mixed up.

Try the following:

svg.linearGradient( $('svg defs'), 
                    'MyGradient', 
                    [ ['5%', '#F60'], ['95%', '#FF6']] );

(Not exactly sure, however. You might need to fiddle around a bit with that code.)

EDIT

Just created this fiddle in order to test the thesis (as suggested by @Phrogz). Indeed it returns http://www.w3.org/1999/xhtml as the namespace for the inserted <linearGradient>, which is the wrong namespace and thus validates my above speculation.

Upvotes: 3

Miki
Miki

Reputation: 469

Found a solution. It's a bit ugly, but doesn't require the use of additional plugins.

Apparently, a pattern has to be included in the tag when the SVG is first created (it's probably only read then).

Thus, replacing the SVG tag's wrapper's contents with themselves works (base being that wrapper):

$(base).html($(base).html())

Upvotes: 3

Related Questions