jQuery SVG, tại sao tôi không thể addClass?


289

Tôi đang sử dụng jQuery SVG. Tôi không thể thêm hoặc xóa một lớp cho một đối tượng. Có ai biết lỗi của tôi không?

SVG:

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

JQuery sẽ không thêm lớp:

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

Tôi biết SVG và jQuery đang hoạt động tốt với nhau vì tôi có thể nhắm mục tiêu vào đối tượng và đưa ra cảnh báo khi nhấp vào:

$(".jimmy").click(function() {
    alert('Handler for .click() called.');
});

4
Bài viết hay ở đây: babmotto.com/ từ
Kris

8
Vì vậy, câu hỏi thực tế TẠI SAO tôi không thể sử dụng các hàm lớp jQuery * vẫn chưa được trả lời ... Nếu tôi có thể truy cập các thuộc tính của các phần tử SVG thông qua hàm attr jQuery, tại sao các hàm lớp * cũng không làm được điều này? jQuery FAIL?!?
Ryan Griggs

2
Nghiêm túc mà nói, có ai biết TẠI SAO ??
Ben Wilde

Thư viện nhỏ bổ sung chức năng này cho các tệp SVG: github.com/toddmotto/lunar
Chuck Le Mông

6
"Tại sao" là do jQuery sử dụng thuộc tính "className", thuộc tính chuỗi cho các phần tử HTML, nhưng là Chuỗi hoạt hình SVG cho các phần tử SVG. Mà không thể được chỉ định để thích cho các yếu tố HTML. Do đó, mã jQuery thổi lên cố gắng gán giá trị.
Jon Watte

Câu trả lời:


426

Chỉnh sửa 2016: đọc hai câu trả lời tiếp theo.

  • JQuery 3 khắc phục sự cố cơ bản
  • Vanilla JS: element.classList.add('newclass')hoạt động trong các trình duyệt hiện đại

JQuery (dưới 3) không thể thêm một lớp vào một SVG.

.attr() hoạt động với SVG, vì vậy nếu bạn muốn phụ thuộc vào jQuery:

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

Và nếu bạn không muốn phụ thuộc vào jQuery:

var element = document.getElementById("item");
// Instead of .addClass("newclass")
element.setAttribute("class", "oldclass newclass");
// Instead of .removeClass("newclass")
element.setAttribute("class", "oldclass");

Để hỗ trợ cho đề xuất forresto
helloworld

hoàn hảo. đây sẽ là câu trả lời mặc dù câu trả lời THỰC SỰ dành cho jquery để bao gồm điều này theo mặc định, ít nhất là một cài đặt hoặc một cái gì đó.
AwokeKnowing

2
.attr("class", "oldclass")(hoặc cồng kềnh hơn .setAttribute("class", "oldclass")) thực sự không tương đương với việc loại bỏ newclass!
Tom

Câu trả lời tuyệt vời (và kỹ thuật v.nice) ... nhưng sẽ là một ý tưởng để chỉnh sửa câu hỏi để nó bắt đầu bằng cách nói rằng jquery không thể thêm một lớp vào một SVG (nếu đó là ... )?
byronyasgur

1
Chỉ là một phụ lục: Tôi đã thử và JQuery 3 không khắc phục được sự cố.
Joao Arruda

76

Có phần tử. ClassList trong API DOM hoạt động cho cả các phần tử HTML và SVG. Không cần plugin jQuery SVG hoặc thậm chí jQuery.

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

3
Tôi đã thử nghiệm điều này và. ClassList dường như không được xác định cho các thành phần phụ của Svg, ít nhất là trong Chrome.
Chris Jaynes

3
Hoạt động với tôi trong Chrome. Tôi có SVG được nhúng trong HTML, vì vậy cấu trúc tài liệu của tôi trông như thế này: <html> <svg> <circle class = "jimmy" ...> </ svg> </ html>
Tomas Mikula

1
Tôi đã sử dụng điều này để thêm lớp trong phần tử <g> SVG, hoạt động tốt trong Chrome.
Rachelle Uy

20
IE không triển khai. ClassList và API trên các phần tử SVG. Xem caniuse.com/#feat= classlist và danh sách "các vấn đề đã biết". Điều đó cũng đề cập đến các trình duyệt WebKit.
Shenme

9
Và khi thời gian trôi qua, câu trả lời này càng trở nên đúng hơn (và câu trả lời hay nhất)
Gerrat

69

jQuery 3 không có vấn đề này

Một trong những thay đổi được liệt kê trên các bản sửa đổi jQuery 3.0 là:

thêm thao tác lớp SVG ( # 2199 , 20aaed3 )

Một giải pháp cho vấn đề này là nâng cấp lên jQuery 3. Nó hoạt động rất tốt:

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>


Vấn đề:

Lý do các hàm thao tác lớp jQuery không hoạt động với các phần tử SVG là do các phiên bản jQuery trước 3.0 sử dụng thuộc classNametính cho các hàm này.

Trích từ jQuery attributes/classes.js:

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

Điều này hoạt động như mong đợi đối với các phần tử HTML, nhưng đối với các phần tử SVG classNamethì hơi khác một chút. Đối với một phần tử SVG, classNamekhông phải là một chuỗi, mà là một thể hiện của SVGAnimatedString.

Hãy xem xét các mã sau đây:

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>

Nếu bạn chạy mã này, bạn sẽ thấy một cái gì đó như sau trong bảng điều khiển dành cho nhà phát triển của bạn.

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

Nếu chúng ta truyền SVGAnimatedStringđối tượng đó thành một chuỗi như jQuery, chúng ta sẽ có [object SVGAnimatedString], đó là nơi jQuery thất bại.

Cách plugin jQuery SVG xử lý việc này:

Plugin jQuery SVG hoạt động xung quanh điều này bằng cách vá các chức năng có liên quan để thêm hỗ trợ SVG.

Trích từ jQuery SVG jquery.svgdom.js:

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

Hàm này sẽ phát hiện xem một phần tử có phải là phần tử SVG hay không và nếu có thì nó sẽ sử dụng thuộc baseValtính của SVGAnimatedStringđối tượng nếu có, trước khi quay lại classthuộc tính.

Lập trường lịch sử của jQuery về vấn đề này:

jQuery hiện liệt kê vấn đề này trên trang W't Fix của họ . Đây là những phần có liên quan.

Lỗi SVG / VML hoặc không gian tên

jQuery chủ yếu là một thư viện cho HTML DOM, vì vậy hầu hết các vấn đề liên quan đến tài liệu SVG / VML hoặc các phần tử được đặt tên đều nằm ngoài phạm vi. Chúng tôi cố gắng giải quyết các vấn đề "chảy qua" cho các tài liệu HTML, chẳng hạn như các sự kiện bong bóng ra khỏi SVG.

Rõ ràng jQuery đã xem xét hỗ trợ SVG đầy đủ bên ngoài phạm vi của lõi jQuery và phù hợp hơn với các plugin.


38

Nếu bạn có các lớp động hoặc không biết những lớp nào có thể được áp dụng thì phương pháp này tôi tin là cách tiếp cận tốt nhất:

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

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

1
Đây là một cứu cánh cho tôi vì tôi cần thay đổi nhiều yếu tố SVG để hoàn thành công việc. +999 từ tôi :)
Karl

Thật khó để tin rằng jQuery vẫn sẽ tiếp tục làm việc này, thậm chí sau 2 năm và việc phát hành 2.xx Bản sửa lỗi của bạn, điều hướng, đã tiết kiệm cho tôi một ngày. Phần còn lại của các bản sửa lỗi là phức tạp không cần thiết. Cảm ơn!
vô song

17

Dựa trên các câu trả lời trên, tôi đã tạo API sau

/*
 * .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
Điều này rất hữu ích, nhưng các thuật toán có vấn đề: 1. Nếu không có chuỗi lớp hiện có, bạn sẽ nhận được 'className chưa được xác định' khi thêm một lớp. 2. Nếu chuỗi lớp chứa một lớp là siêu lớp của biểu thức chính quy, bạn để lại rác trong chuỗi lớp. Ví dụ: nếu bạn cố gắng loại bỏ lớp 'chó' và chuỗi lớp chứa 'thức ăn cho chó', bạn sẽ bị bỏ lại với 'thức ăn' trong chuỗi lớp. Dưới đây là một số cách khắc phục: jsfiddle.net/hellobrett/c2c2zfto
Brett

1
Đây không phải là một giải pháp hoàn hảo. Nó thay thế tất cả các từ có chứa lớp bạn muốn loại bỏ.
tom10271

13

Sau khi tải, jquery.svg.jsbạn phải tải tập tin này : http://keith-wood.name/js/jquery.svgdom.js.

Nguồn: http://keith-wood.name/svg.html#dom

Ví dụ hoạt động: http://jsfiddle.net/74RbC/99/


Đã thêm nó - dường như vẫn không hoạt động. Tôi đã xem qua các tài liệu trên trang web của SVG, nhưng không có lời giải thích rõ ràng về những gì bạn cần làm để sử dụng chức năng bổ sung của nó.
Don P

7

Chỉ cần thêm hàm tạo nguyên mẫu bị thiếu vào tất cả các nút 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);
  }
};

Sau đó, bạn có thể sử dụng nó theo cách này mà không yêu cầu jQuery:

this.addClass('clicked');

this.removeClass('clicked');

Tất cả tín dụng thuộc về Todd Moto .


tức là11 cuộn cảm trên mã này. polyfill này là một tuyến tốt hơn: github.com/eligrey/
classList.js

5

jQuery không hỗ trợ các lớp của các phần tử SVG. Bạn có thể lấy phần tử trực tiếp $(el).get(0)và sử dụng classListadd / remove. Cũng có một mẹo với điều này là phần tử SVG trên cùng thực sự là một đối tượng DOM bình thường và có thể được sử dụng như mọi phần tử khác trong jQuery. Trong dự án của tôi, tôi đã tạo ra điều này để quan tâm đến những gì tôi cần nhưng tài liệu được cung cấp trên Mozilla Developer Network có một shim có thể được sử dụng thay thế.

thí dụ

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

Một cách khác tất cả khó khăn hơn là sử dụng D3.js làm công cụ chọn của bạn. Các dự án của tôi có các biểu đồ được xây dựng cùng với nó vì vậy nó cũng nằm trong phạm vi ứng dụng của tôi. D3 sửa đổi chính xác các thuộc tính lớp của các phần tử vanilla DOM và các phần tử SVG. Mặc dù thêm D3 cho trường hợp này có thể sẽ là quá mức cần thiết.

d3.select(el).classed('myclass', true);

1
Cảm ơn vì d3 bit đó ... Tôi thực sự đã có d3 trên trang nên mới quyết định sử dụng nó. Rất hữu ích :)
Muers

5

jQuery 2.2 hỗ trợ thao tác lớp SVG

Các jQuery 2.2 và 1.12 Phát hành bài bao gồm các trích dẫn sau đây:

Mặc dù jQuery là một thư viện HTML, chúng tôi đã đồng ý rằng hỗ trợ lớp cho các phần tử SVG có thể hữu ích. Bây giờ người dùng sẽ có thể gọi các phương thức .addClass () , .removeClass () , .toggleClass ().hasClass () trên SVG. Bây giờ jQuery thay đổi thuộc tính lớp thay vì thuộc tính className . Điều này cũng làm cho các phương thức lớp có thể sử dụng được trong các tài liệu XML nói chung. Hãy nhớ rằng nhiều thứ khác sẽ không hoạt động với SVG và chúng tôi vẫn khuyên bạn nên sử dụng thư viện dành riêng cho SVG nếu bạn cần bất cứ điều gì ngoài thao tác lớp.

Ví dụ sử dụng jQuery 2.2.0

Nó kiểm tra:

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

Nếu bạn nhấp vào hình vuông nhỏ đó, nó sẽ thay đổi màu sắc vì classthuộc tính được thêm / xóa.

$("#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

Đây là mã khá không phù hợp nhưng hoạt động của tôi liên quan đến các vấn đề sau (không có bất kỳ phụ thuộc nào):

  • classListkhông tồn tại trên <svg>các phần tử trong IE
  • classNamekhông đại diện cho classthuộc tính trên <svg>các thành phần trong IE
  • IE cũ bị hỏng getAttribute()setAttribute()triển khai

Nó sử dụng classListnếu có thể.

Mã số:

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

Ví dụ sử dụng:

var el = document.getElementById("someId");
if (hasClass(el, "someClass")) {
    removeClass(el, "someClass");
}
addClass(el, "someOtherClass");

Bạn đã kiểm tra nó bằng IE7 chưa? Bởi vì LTE IE7 yêu cầu strAttributionName phải là "className" khi cài đặt, nhận hoặc xóa thuộc tính Class .
Knu

@Knu: Nó chắc chắn hoạt động trong IE 6 và 7 cho các phần tử HTML thông thường bởi vì trong các trình duyệt đó, nó sử dụng thuộc classNametính thay vì cố gắng đặt thuộc tính. Lý do "LTE IE7 yêu cầu strAttributionName phải là" className "khi cài đặt, nhận hoặc xóa thuộc tính LỚP" là vì các trình duyệt đó đã triển khai bị hỏng getAttribute(x)setAttribute(x)chỉ cần lấy hoặc đặt thuộc tính với tên x, vì vậy nó dễ dàng và tương thích hơn để đặt tài sản trực tiếp.
Tim Down

@Knu: OK. Tôi không chắc vì SVG không được hỗ trợ trong IE 7, vì vậy tôi không chắc bạn sẽ đạt được gì khi đưa một <svg>yếu tố vào trang được thiết kế để hoạt động trong IE 7. Vì giá trị của nó, mã của tôi không thêm thuộc tính lớp thành công vào <svg>các phần tử trong IE 7 vì một phần tử như vậy hoạt động giống như bất kỳ phần tử tùy chỉnh nào khác.
Tim Down

Hoàn toàn quên về điều đó cảm ơn cho lời nhắc nhở. Tôi sẽ thử dùng Adobe SVG Viewer để kiểm tra xem nó có hoạt động như dự định không.
Knu

2

Tôi sử dụng Snap.svg để thêm một lớp và SVG.

var jimmy = Snap(" .jimmy ")

jimmy.addClass("exampleClass");

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


1
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
Tristan

1

Một cách giải quyết khác có thể là addClass vào một thùng chứa phần tử 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

Tôi đã viết điều này trong dự án của tôi, và nó hoạt động ... có lẽ;)

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

ví dụ

$('path').addSvgClass('fillWithOrange')
$('path').removeSvgClass('fillWithOrange')

0

Lấy cảm hứng từ các câu trả lời ở trên, đặc biệt là Sagar Gala, tôi đã tạo API này . Bạn có thể sử dụng nó nếu bạn không muốn hoặc không thể nâng cấp phiên bản jquery của bạn.


-1

Hoặc chỉ sử dụng các phương thức DOM trường học cũ khi JQ có một con khỉ ở giữa một nơi nào đó.

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

Tìm hiểu người DOM. Ngay cả trong khuôn khổ năm 2016, nó cũng giúp khá thường xuyên. Ngoài ra, nếu bạn từng nghe ai đó so sánh DOM với lắp ráp, hãy đá chúng cho tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.