對avalon的類名操作進行升級

weixin_33831673發表於2014-08-19

在對SVG元素進行類名操作時,發現有一個坑爹的事情,它的className竟然是一個物件,因此報一系列BUG。第一次想到的方法是新增setClasses, getClasses兩個更底層的方法。於是相應程式碼變成:


    var rclass = /\s+/g
    function getClasses(node) {
        if (node && node.className) {
            var classes = node.className//SVG元素是返回一個SVGAnimatedString物件
            if ("baseVal" in classes) {
                classes = classes.baseVal
            }
            return classes.split(rclass)
        }
        return []
    }
    function setClasses(node, cls) {
        if (node && node.nodeType === 1) {
            if ("baseVal" in node.className) {
                node.setAttribute("class", cls)
            } else {
                node.className = cls
            }
        }
    }

    avalon.fn.mix({
        hasClass: function(cls) {
            var classList = getClasses(this[0])
            if (classList.length) {
                return (" " + classList.join(" ") + " ").indexOf(" " + cls + " ") > -1
            }
            return false
        },
        addClass: function(cls) {
            var node = this[0]
            if (cls && node && node.nodeType === 1) {
                var arr = getClasses(node)
                cls.replace(/\S+/g, function(c) {
                    if (arr.indexOf(c) === -1) {
                        arr.push(c)
                    }
                })
                setClasses(node, arr.join(" "))
            }
            return this
        },
        removeClass: function(cls) {
            var node = this[0], classList = getClasses(node)
            if (cls && classList.length) {
                var set = " " + classList.join(" ") + " "
                cls.replace(/\S+/g, function(c) {
                    set = set.replace(" " + c + " ", " ")
                })
                setClasses(node, set.slice(1, set.length - 1))
            }
            return this
        },
        toggleClass: function(value, stateVal) {
            var state = stateVal,
                    className, i = 0
            var classNames = value.split(rclass)
            var isBool = typeof stateVal === "boolean"
            while ((className = classNames[i++])) {
                state = isBool ? state : !this.hasClass(className)
                this[state ? "addClass" : "removeClass"](className)
            }
            return this
        }
})

這裡的麻煩處是,IE6,IE7不能直接通過getAttribute("class")得到className,而SVG如果直接用className又沒有用,於是抽取出getClasses方法,賦值也一樣。

在高階瀏覽器,classList在SVG中是沒有相容問題。看avalon.mobile的相關實現是多麼簡潔:


    "add,remove".replace(rword, function(method) {
        avalon.fn[method + "Class"] = function(cls) {
            var el = this[0]
            //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
            if (cls && typeof cls === "string" && el && el.nodeType == 1) {
                cls.replace(/\S+/g, function(c) {
                    el.classList[method](c)
                })
            }
            return this
        }
    })
    
    avalon.fn.mix({
        hasClass: function(cls) {
            var el = this[0] || {} //IE10+, chrome8+, firefox3.6+, safari5.1+,opera11.5+支援classList,chrome24+,firefox26+支援classList2.0
            return el.nodeType === 1 && el.classList.contains(cls)
        },
        toggleClass: function(value, stateVal) {
            var state = stateVal,
                    className, i = 0
            var classNames = value.split(/\s+/)
            var isBool = typeof stateVal === "boolean"
            var node = this[0] || {}, classList
            if (classList = node.classList) {
                while ((className = classNames[i++])) {
                    state = isBool ? state : !classList.contains(className)
                    classList[state ? "add" : "remove"](className)
                }
            }
            return this
        }
})

因此新的思路來了,為IE6-9等實現一個classList,通過它來遮蔽底層,再在classList的基礎上構建avalon的addClass, removeClass, toggleClass,hasClass


    function ClassList(node) {
        if (!("classList" in node)) {
            node.classList = {
                node: node,
                toString: function() {
                    var node = this.node
                    return (node.hasAttribute ? node.getAttribute("class") || "" : node.className).split(/\s+/).join(" ")
                },
                contains: function(cls) {
                    return (" " + this + " ").indexOf(" " + cls + " ") > -1
                },
                _set: function(cls) {
                    var node = this.node
                    if (typeof node.className == "string") {
                        node.className = cls
                    } else {
                        node.setAttribute("class", cls)
                    }
                },
                add: function(cls) {
                    if (!this.contains(cls)) {
                        this._set(this + " " + cls)
                    }
                },
                remove: function(cls) {
                    this._set((" " + this + " ").replace(" " + cls + " ", " ").trim())
                }
            }
        }
        return node.classList
    }
    "add,remove".replace(rword, function(method) {
        avalon.fn[method + "Class"] = function(cls) {
            var el = this[0]
            //https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox/Releases/26
            if (cls && typeof cls === "string" && el && el.nodeType == 1) {
                cls.replace(/\S+/, function(c) {
                    ClassList(el)[method](c)
                })
            }
            return this
        }
    })
    avalon.fn.mix({
        hasClass: function(cls) {
            var el = this[0] || {}
            return el.nodeType === 1 && ClassList(el).contains(cls)
            return false
        },
        toggleClass: function(value, stateVal) {
            var state = stateVal,
                    className, i = 0
            var classNames = value.split(/\s+/)
            var isBool = typeof stateVal === "boolean"
            while ((className = classNames[i++])) {
                state = isBool ? state : !this.hasClass(className)
                this[state ? "addClass" : "removeClass"](className)
            }
            return this
        }
})
http://baike.baidu.com/view/957693.htm

相關文章