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.');
});

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");

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");
});

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.


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', '');
});

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;
};

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


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 .


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);

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>


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");

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


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>


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')

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.


-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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.