jQuery SVG, mengapa saya tidak bisa menambahkanClass?

289

Saya menggunakan jQuery SVG. Saya tidak bisa menambah atau menghapus kelas ke objek. Adakah yang tahu kesalahan saya?

SVG:

<rect class="jimmy" id="p5" x="200" y="200" width="100" height="100" />

JQuery yang tidak akan menambah kelas:

$(".jimmy").click(function() {
    $(this).addClass("clicked");
});

Saya tahu SVG dan jQuery bekerja bersama dengan baik karena saya dapat menargetkan objek dan mengaktifkan peringatan ketika diklik:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});
Don P
sumber
4
Artikel bagus di sini: toddmotto.com/...
Kris
8
Jadi pertanyaan sebenarnya MENGAPA saya tidak dapat menggunakan fungsi kelas * jQuery tetap tidak terjawab ... Jika saya dapat mengakses properti elemen SVG melalui fungsi attr jQuery, mengapa fungsi kelas * juga tidak dapat melakukan ini? jQuery FAIL?!?
Ryan Griggs
2
Serius, apakah ada yang tahu MENGAPA ??
Ben Wilde
Pustaka kecil yang menambahkan fungsi ini untuk file SVG: github.com/toddmotto/lunar
Chuck Le Butt
6
The "why" adalah karena jQuery menggunakan properti "className", yang merupakan properti string untuk elemen HTML, tetapi merupakan String Animasi SVG untuk elemen SVG. Yang tidak dapat ditugaskan untuk suka untuk elemen HTML. Dengan demikian, kode jQuery meledak mencoba menetapkan nilai.
Jon Watte

Jawaban:

426

Edit 2016: baca dua jawaban berikutnya.

  • JQuery 3 memperbaiki masalah yang mendasarinya
  • Vanilla JS: element.classList.add('newclass')berfungsi di peramban modern

JQuery (kurang dari 3) tidak dapat menambahkan kelas ke SVG.

.attr() bekerja dengan SVG, jadi jika Anda ingin bergantung pada jQuery:

// Instead of .addClass("newclass")
$("#item").attr("class", "oldclass newclass");
// Instead of .removeClass("newclass")
$("#item").attr("class", "oldclass");

Dan jika Anda tidak ingin bergantung pada jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");
forresto
sumber
Untuk mendukung saran forresto
helloworld
sempurna. ini seharusnya jawabannya. meskipun jawaban NYATA untuk jquery untuk memasukkan ini secara default, setidaknya sebagai pengaturan atau sesuatu
AwokeKnowing
2
.attr("class", "oldclass")(atau lebih rumit .setAttribute("class", "oldclass")) benar-benar tidak setara dengan menghapus newclass!
Tom
Jawaban yang bagus (dan teknik v.nice) ... tetapi apakah itu ide untuk mengedit pertanyaan sehingga dimulai dengan menyatakan bahwa jquery tidak dapat menambahkan kelas ke SVG (jika itu masalahnya ... sepertinya )?
byronyasgur
1
Hanya tambahan: Saya mencoba dan JQuery 3 tidak memperbaiki masalah.
Joao Arruda
76

Ada element.classList di DOM API yang berfungsi untuk elemen HTML dan SVG. Tidak perlu untuk plugin jQuery SVG atau bahkan jQuery.

$(".jimmy").click(function() {
    this.classList.add("clicked");
});
Tomas Mikula
sumber
3
Saya menguji ini, dan .classList tampaknya tidak terdefinisi untuk sub-elemen svg, setidaknya di Chrome.
Chris Jaynes
3
Bekerja untuk saya di Chrome. Saya memiliki SVG yang tertanam dalam HTML, sehingga struktur dokumen saya terlihat seperti ini: <html> <svg> <circle class = "jimmy" ...> </svg> </html>
Tomas Mikula
1
Saya menggunakan ini untuk menambahkan kelas di elemen <g> SVG, berfungsi dengan baik di Chrome.
Rachelle Uy
20
IE tidak mengimplementasikan .classList dan API pada SVG Elements. Lihat caniuse.com/#feat=classlist dan daftar "masalah yang diketahui". Itu menyebutkan browser WebKit juga.
Shenme
9
Dan seiring berjalannya waktu, jawaban ini menjadi lebih benar (dan jawaban terbaik)
Gerrat
69

jQuery 3 tidak memiliki masalah ini

Salah satu perubahan yang tercantum pada revisi jQuery 3.0 adalah:

tambahkan manipulasi kelas SVG ( # 2199 , 20aaed3 )

Salah satu solusi untuk masalah ini adalah meng-upgrade ke jQuery 3. Ini berfungsi dengan baik:

var flip = true;
setInterval(function() {
    // Could use toggleClass, but demonstrating addClass.
    if (flip) {
        $('svg circle').addClass('green');
    }
    else {
        $('svg circle').removeClass('green');
    }
    flip = !flip;
}, 1000);
svg circle {
    fill: red;
    stroke: black;
    stroke-width: 5;
}
svg circle.green {
    fill: green;
}
<script src="https://code.jquery.com/jquery-3.0.0.min.js"></script>

<svg>
    <circle cx="50" cy="50" r="25" />
</svg>


Masalah:

Alasan fungsi manipulasi kelas jQuery tidak berfungsi dengan elemen SVG adalah karena versi jQuery sebelum 3.0 menggunakan classNameproperti untuk fungsi-fungsi ini.

Kutipan dari jQuery attributes/classes.js:

cur = elem.nodeType === 1 && ( elem.className ?
    ( " " + elem.className + " " ).replace( rclass, " " ) :
    " "
);

Ini berlaku seperti yang diharapkan untuk elemen HTML, tetapi untuk elemen SVG classNamesedikit berbeda. Untuk elemen SVG, classNamebukan string, tetapi sebuah instance dari SVGAnimatedString.

Pertimbangkan kode berikut:

var test_div = document.getElementById('test-div');
var test_svg = document.getElementById('test-svg');
console.log(test_div.className);
console.log(test_svg.className);
#test-div {
    width: 200px;
    height: 50px;
    background: blue;
}
<div class="test div" id="test-div"></div>

<svg width="200" height="50" viewBox="0 0 200 50">
  <rect width="200" height="50" fill="green" class="test svg" id="test-svg" />
</svg>

Jika Anda menjalankan kode ini, Anda akan melihat sesuatu seperti yang berikut di konsol pengembang Anda.

test div
SVGAnimatedString { baseVal="test svg",  animVal="test svg"}

Jika kita melemparkan SVGAnimatedStringobjek itu ke string seperti yang dilakukan jQuery, kita harus melakukannya [object SVGAnimatedString], di mana jQuery gagal.

Bagaimana plugin jQuery SVG menangani ini:

Plugin jQuery SVG bekerja di sekitar ini dengan menambal fungsi yang relevan untuk menambahkan dukungan SVG.

Kutipan dari jQuery SVG jquery.svgdom.js:

function getClassNames(elem) {
    return (!$.svg.isSVGElem(elem) ? elem.className :
        (elem.className ? elem.className.baseVal : elem.getAttribute('class'))) || '';
}

Fungsi ini akan mendeteksi jika suatu elemen adalah elemen SVG, dan jika itu akan menggunakan baseValproperti SVGAnimatedStringobjek jika tersedia, sebelum jatuh kembali pada classatribut.

Sikap historis jQuery tentang masalah ini:

jQuery saat ini mencantumkan masalah ini di halaman Won't Fix mereka . Inilah bagian yang relevan.

Bug Elemen SVG / VML atau Namespaced

jQuery terutama merupakan pustaka untuk HTML DOM, jadi sebagian besar masalah yang terkait dengan dokumen SVG / VML atau elemen namespace berada di luar jangkauan. Kami mencoba untuk mengatasi masalah yang "berdarah" ke dokumen HTML, seperti peristiwa yang keluar dari SVG.

Jelas jQuery dianggap sebagai dukungan SVG penuh di luar lingkup inti jQuery, dan lebih cocok untuk plugin.

Alexander O'Mara
sumber
38

Jika Anda memiliki kelas dinamis atau tidak tahu kelas apa yang bisa diterapkan maka metode ini saya percaya adalah pendekatan terbaik:

// addClass
$('path').attr('class', function(index, classNames) {
    return classNames + ' class-name';
});

// removeClass
$('path').attr('class', function(index, classNames) {
    return classNames.replace('class-name', '');
});
nav
sumber
1
Ini adalah penyelamat bagi saya karena saya perlu mengubah banyak elemen SVG untuk menyelesaikan sesuatu. +999 dari saya :)
Karl
Sulit untuk percaya bahwa jQuery masih akan mengalami kesulitan dalam hal ini, bahkan setelah 2 tahun dan rilis 2.xx Perbaikan Anda, tidak, menyelamatkan hari untuk saya. Sisa dari perbaikan ini tidak perlu rumit. Terima kasih!
unrivaledcreations
17

Berdasarkan jawaban di atas saya membuat API berikut

/*
 * .addClassSVG(className)
 * Adds the specified class(es) to each of the set of matched SVG elements.
 */
$.fn.addClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        return ((existingClassNames !== undefined) ? (existingClassNames + ' ') : '') + className;
    });
    return this;
};

/*
 * .removeClassSVG(className)
 * Removes the specified class to each of the set of matched SVG elements.
 */
$.fn.removeClassSVG = function(className){
    $(this).attr('class', function(index, existingClassNames) {
        var re = new RegExp('\\b' + className + '\\b', 'g');
        return existingClassNames.replace(re, '');
    });
    return this;
};
Sagar Gala
sumber
3
Ini membantu, tetapi algoritmanya memiliki masalah: 1. Jika tidak ada string kelas yang ada, Anda mendapatkan 'undedfined className' setelah menambahkan kelas. 2. Jika string kelas berisi kelas yang merupakan superset dari ekspresi reguler, Anda meninggalkan sampah di string kelas. Misalnya, jika Anda mencoba untuk menghapus 'dog' kelas dan string kelas berisi 'dogfood', Anda akan ditinggalkan dengan 'makanan' di string kelas. Berikut adalah beberapa perbaikan: jsfiddle.net/hellobrett/c2c2zfto
Brett
1
Ini bukan solusi yang sempurna. Itu mengganti semua kata yang mengandung kelas yang ingin Anda hapus.
tom10271
13

Setelah memuat, jquery.svg.jsAnda harus memuat file ini:http://keith-wood.name/js/jquery.svgdom.js .

Sumber: http://keith-wood.name/svg.html#dom

Contoh kerja: http://jsfiddle.net/74RbC/99/

bennedich
sumber
Menambahkannya - masih tidak berfungsi. Saya telah melihat dokumentasi di situs jquery SVG, tetapi tidak ada penjelasan yang jelas tentang apa yang perlu Anda lakukan untuk memanfaatkan fungsionalitas ekstra.
Don P
7

Cukup tambahkan konstruktor prototipe yang hilang ke semua node SVG:

SVGElement.prototype.hasClass = function (className) {
  return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.getAttribute('class'));
};

SVGElement.prototype.addClass = function (className) { 
  if (!this.hasClass(className)) {
    this.setAttribute('class', this.getAttribute('class') + ' ' + className);
  }
};

SVGElement.prototype.removeClass = function (className) {
  var removedClass = this.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2');
  if (this.hasClass(className)) {
    this.setAttribute('class', removedClass);
  }
};

Anda kemudian dapat menggunakannya dengan cara ini tanpa memerlukan jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Semua kredit diberikan kepada Todd Moto .

Ziad
sumber
ie11 tersedak kode ini. polyfill ini adalah rute yang lebih baik: github.com/eligrey/classList.js
ryanrain
5

jQuery tidak mendukung kelas elemen SVG. Anda bisa mendapatkan elemen secara langsung $(el).get(0)dan menggunakan classListdan add / remove. Ada trik dengan ini juga dalam elemen SVG paling atas sebenarnya adalah objek DOM normal dan dapat digunakan seperti setiap elemen lainnya di jQuery. Dalam proyek saya, saya membuat ini untuk mengurus apa yang saya butuhkan tetapi dokumentasi yang disediakan di Mozilla Developer Network memiliki shim yang dapat digunakan sebagai alternatif.

contoh

function addRemoveClass(jqEl, className, addOrRemove) 
{
  var classAttr = jqEl.attr('class');
  if (!addOrRemove) {
    classAttr = classAttr.replace(new RegExp('\\s?' + className), '');
    jqEl.attr('class', classAttr);
  } else {
    classAttr = classAttr + (classAttr.length === 0 ? '' : ' ') + className;
    jqEl.attr('class', classAttr);
  }
}

Alternatif yang lebih sulit adalah menggunakan D3.js sebagai mesin pemilih Anda. Proyek saya memiliki grafik yang dibuat dengan itu sehingga juga dalam lingkup aplikasi saya. D3 dengan benar memodifikasi atribut kelas dari elemen DOM vanilla dan elemen SVG. Meskipun menambahkan D3 untuk kasus ini kemungkinan akan berlebihan.

d3.select(el).classed('myclass', true);
QueueHammer
sumber
1
Terima kasih untuk itu sedikit d3 ... Saya sebenarnya sudah punya d3 pada halaman jadi hanya memutuskan untuk menggunakannya. Sangat berguna :)
Muers
5

jQuery 2.2 mendukung manipulasi kelas SVG

The jQuery 2.2 dan 1.12 Dirilis pasca termasuk kutipan berikut:

Sementara jQuery adalah pustaka HTML, kami sepakat bahwa dukungan kelas untuk elemen SVG bisa berguna. Pengguna sekarang dapat memanggil metode .addClass () , .removeClass () , .toggleClass () , dan .hasClass () di SVG. jQuery sekarang mengubah atribut class daripada properti className . Ini juga membuat metode kelas dapat digunakan dalam dokumen XML umum. Perlu diingat bahwa banyak hal lain tidak akan berfungsi dengan SVG, dan kami masih merekomendasikan menggunakan perpustakaan yang didedikasikan untuk SVG jika Anda memerlukan sesuatu di luar manipulasi kelas.

Contoh menggunakan jQuery 2.2.0

Ini menguji:

  • .addClass ()
  • .removeClass ()
  • .hasClass ()

Jika Anda mengklik kotak kecil itu, itu akan berubah warna karena classatribut ditambahkan / dihapus.

$("#x").click(function() {
    if ( $(this).hasClass("clicked") ) {
        $(this).removeClass("clicked");
    } else {
        $(this).addClass("clicked");
    }
});
.clicked {
    fill: red !important;  
}
<html>

<head>
    <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
</head>

<body>
    <svg width="80" height="80">
        <rect id="x" width="80" height="80" style="fill:rgb(0,0,255)" />
    </svg>
</body>

</html>

ROMANIA_engineer
sumber
3

Berikut ini adalah kode saya yang agak tidak berlaku tetapi berfungsi yang berhubungan dengan masalah-masalah berikut (tanpa ketergantungan):

  • classListtidak ada pada <svg>elemen di IE
  • classNametidak mewakili classatribut pada <svg>elemen di IE
  • IE lama rusak getAttribute()dan setAttribute()implementasi

Ini menggunakan classListjika memungkinkan.

Kode:

var classNameContainsClass = function(fullClassName, className) {
    return !!fullClassName &&
           new RegExp("(?:^|\\s)" + className + "(?:\\s|$)").test(fullClassName);
};

var hasClass = function(el, className) {
    if (el.nodeType !== 1) {
        return false;
    }
    if (typeof el.classList == "object") {
        return (el.nodeType == 1) && el.classList.contains(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ? el.className : el.getAttribute("class");
        return classNameContainsClass(elClass, className);
    }
};

var addClass = function(el, className) {
    if (el.nodeType !== 1) {
        return;
    }
    if (typeof el.classList == "object") {
        el.classList.add(className);
    } else {
        var classNameSupported = (typeof el.className == "string");
        var elClass = classNameSupported ?
            el.className : el.getAttribute("class");
        if (elClass) {
            if (!classNameContainsClass(elClass, className)) {
                elClass += " " + className;
            }
        } else {
            elClass = className;
        }
        if (classNameSupported) {
            el.className = elClass;
        } else {
            el.setAttribute("class", elClass);
        }
    }
};

var removeClass = (function() {
    function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
        return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
    }

    return function(el, className) {
        if (el.nodeType !== 1) {
            return;
        }
        if (typeof el.classList == "object") {
            el.classList.remove(className);
        } else {
            var classNameSupported = (typeof el.className == "string");
            var elClass = classNameSupported ?
                el.className : el.getAttribute("class");
            elClass = elClass.replace(new RegExp("(^|\\s)" + className + "(\\s|$)"), replacer);
            if (classNameSupported) {
                el.className = elClass;
            } else {
                el.setAttribute("class", elClass);
            }
        }
    }; //added semicolon here
})();

Contoh penggunaan:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");
Tim Down
sumber
Apakah Anda mengujinya menggunakan IE7? Karena LTE IE7 membutuhkan strAttributeName untuk menjadi "className" ketika mengatur, mendapatkan, atau menghapus atribut CLASS .
Knu
@ Knu: Ini pasti berfungsi di IE 6 dan 7 untuk elemen HTML biasa karena di browser itu menggunakan classNameproperti daripada mencoba mengatur atribut. Alasan "LTE IE7 membutuhkan strAttributeName menjadi" className "saat mengatur, mendapatkan, atau menghapus atribut CLASS" adalah karena browser tersebut memiliki implementasi yang rusak getAttribute(x)dan setAttribute(x)yang hanya mendapatkan atau mengatur properti dengan nama x, sehingga lebih mudah dan lebih kompatibel untuk mengatur properti secara langsung.
Tim Down
@ Knu: Oke. Saya tidak yakin karena SVG tidak didukung di IE 7, jadi saya tidak yakin apa yang ingin Anda capai dengan memasukkan <svg>elemen di halaman yang dirancang untuk bekerja di IE 7. Untuk apa nilainya, kode saya tidak berhasil menambahkan atribut kelas ke <svg>elemen di IE 7 karena elemen tersebut berperilaku seperti elemen kustom lainnya.
Tim Down
Benar-benar lupa tentang itu, terima kasih atas pengingatnya. Saya akan mencoba untuk mendapatkan Adobe SVG Viewer untuk memeriksa apakah berfungsi sebagaimana dimaksud.
Knu
2

Saya menggunakan Snap.svg untuk menambahkan kelas ke dan SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

http://snapsvg.io/docs/#Element.addClass

cjohndesign
sumber
1
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah.
Tristan
1

Salah satu solusinya adalah dengan menambahkanClass ke wadah elemen svg:

$('.svg-container').addClass('svg-red');
.svg-red svg circle{
    fill: #ED3F32;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="svg-container">
  <svg height="40" width="40">
      <circle cx="20" cy="20" r="20"/>
  </svg>
</div>

claytronicon
sumber
1

Saya menulis ini di proyek saya, dan itu berhasil ... mungkin;)

$.fn.addSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        if(attr.indexOf(className) < 0) {
            $(this).attr('class', attr+' '+className+ ' ')
        }
    })
};    

$.fn.removeSvgClass = function(className) {

    var attr
    this.each(function() {
        attr = $(this).attr('class')
        attr = attr.replace(className , ' ')
        $(this).attr('class' , attr)
    })
};    

contoh

$('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')
Dariusz Majchrzak
sumber
0

Terinspirasi oleh jawaban di atas, terutama oleh Sagar Gala, saya telah membuat API ini . Anda dapat menggunakannya jika Anda tidak ingin atau tidak dapat meningkatkan versi jquery Anda.

Stonecrusher
sumber
-1

Atau hanya menggunakan metode DOM sekolah lama ketika JQ memiliki monyet di tengah suatu tempat.

var myElement = $('#my_element')[0];
var myElClass = myElement.getAttribute('class').split(/\s+/g);
//splits class into an array based on 1+ white space characters

myElClass.push('new_class');

myElement.setAttribute('class', myElClass.join(' '));

//$(myElement) to return to JQ wrapper-land

Pelajari orang DOM. Bahkan dalam framework-palooza 2016 sangat membantu. Juga, jika Anda pernah mendengar seseorang membandingkan DOM dengan perakitan, tendang mereka untuk saya.

Erik Reppen
sumber