事起缘由

6月21日晚,古哥给咱们科普页面渲染机制、JS的一些性能优化和MV*架构,收益匪浅。在培训现场,古哥请船长当场用原生JS实现点击盒子随机变色。船长的编程思路让人脑洞有点大开,Get到不少干货。借着这个例子,古哥不断灌输各种性能优化、浏览器兼容写法、代码整洁等概念。想不到一个如此简单的例子竟有这么大的学问。于是培训归来当晚,当即打开电脑,把这个例子自己实现了一遍。于是便有了此文。

下面我来讲讲我是怎么做的。

一. 首先搭好HTML结构和CSS样式

<!DOCTYPE html>
<html>
<head>
    <title>changeColor</title>
    <style>
        .colorBox {
            width: 200px;
            height: 200px;
            margin: 20px;
            background-color : rgb(100, 200, 100);
            cursor: pointer;
        }
    </style>
</head>
<body>
<div id="colorBox" class="colorBox"></div>
</body>
</html>

接下来肯定是要用JS控制DOM元素啦。

二. 创造随机颜色生成器

首先,需要明确使用何种色彩模式。在HTML标准中,色彩模式有十六进制、RGB、HSL、RGBA、HSLA等。其中,HSL和HSLA不能兼容IE6-8。

1. 创建随机数

因为颜色是随机生成的,因此需要用到Math.random()函数。由于Math.random()生成0~1之间的随机数(包括0不包括1),有时并不能满足我们的需求,因此可能需要乘除一个数。如果希望得到整数,需要对随机数进行取整,可用Math.ceil()【向上取整】或Math.floor()【向下取整】或Math.round()【四舍五入】,或者paseInt()。

举个实用的栗子,如何获得两个数之间的随机数:

function getRandom(min, max) {
    return Math.round(Math.random()*(max - min) + min);
}

Math.random()(max – min) + min应该还是比较好理解的,用底数(min)加上随机偏移量(Math.random()(max – min))就是随机数的大小。

为了使随机数看起来更随机一些,建议使用Math.round()。因为譬如使用Math.ceil()取整的话,min能被娶到的几率非常非常小,只有Math.random()=0时才能取到。同理Math.floor()和paseInt()将永远也取不到max值。

当然,JavaScript中的随机数Math.random()并非真正意义上的随机,只能算“伪随机数”,这与它的产生算法有关,这里不展开讨论。不过Math.random()应付一般的不那么精准的项目中已经够用了。

2. 创建随机颜色

其实使用哪一种色彩模式的实现方式差不多,只是字符串的拼接结果不一样而已。

(1) 使用十六进制色彩模式

直接上代码:

function randomColor() {
    // 这里使用十六进制颜色值
    var colorArr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
    var colorVal = "#";
    for(i = 0; i < 6; i++) {
        colorVal += colorArr[Math.round(15*Math.random())];
    }
    return colorVal;
}

(2) 使用RGB色彩模式

直接上代码:

function randomColor() {
    // 这里使用rgb颜色值
    var colorVal = "rgb(";
    for(i = 0; i < 3; i++) {

        colorVal += Math.round(250*Math.random());
        if (i < 2) {
            colorVal += ",";
        }
    }
    colorVal += ")";
    return colorVal;
}

当然,你还可以使用rgba定义它的透明度。

(3) 使用HSL色彩模式

直接上代码:

function randomColor() {
    // 这里使用HSL颜色值,但HSL不兼容IE6~8
    // HSL(H, S, L),H的取值为0-360,S为0-100%,L为0-100%
    // 在本例子中先设定好饱和度(S)和亮度(L),随机变化色相(H)
    var colorVal,
        S = "50%",
        L = "50%";
    colorVal = "HSL(" + Math.round(360*Math.random()) + "," + S + "," + L + ")";
    return colorVal;
}

3. 绑定样式修改

这里需要修改盒子的background-color。我们可以直接操作<style>标签中的元素,不过最简单的当然是直接在html中标签的style属性啦。

this.style.backgroundColor = randomColor();

4. 将使盒子变色的函数封装

function changeColor() {
    // 创造随机颜色
    function randomColor() {
        // ...
    }
    this.style.backgroundColor = randomColor();
}

三. 给盒子绑定changeColor()事件

1. 获取DOM元素

获取DOM元素的几种方法可以用document.getElementById()、document.getElementByName()、document.getElementsByTagName()。当然还有高性能的document.querySelector(),并能兼容IE8及其以上浏览器。

在这里,我使用原始的document.getElementById()。在一个页面中经常需要获取元素id的方法,所以可以考虑将其封装成函数。见下:

// 获取元素的id值
function getId(id) {
    return document.getElementById(id);
}

2. 对事件进行绑定

(1) 使用基于DOM对象的属性方式

最让人容易想到的时间绑定方式就是ele.onclick = function(){},并且这种基于DOM对象的属性方式能轻易地兼容IE6及其以上浏览器,非常好用。

colorBox.onclick = function() {
    changeColor();
}

(2) 使用基于DOM对象的方法方式
绑定事件,addEventListener不能兼容IE6-8,因此IE6-8需要使用attachEvent,除了函数名、参数的不同,还有个关于this指针的差异。在其它高版本浏览器中,绑定的事件处理函数被调用时,this指向事件绑定的object。而IE中,this指向window对象,通过使用call或apply可以改变this指针的指向。

addEventListener语法:target.addEventListener(event, callback, useCapture);

attachEvent语法:target.attachEvent(event, fn);

另外,再来掰一掰addEventListener的事件流:

当一个事件发生时,分为三个阶段:

a.捕获阶段: 从根节点开始顺序而下,检测每个节点是否注册了事件处理程序。如果注册了事件处理程序,并且 useCapture 为 true,则调用该事件处理程序;(IE 中无此阶段);

b.目标阶段: 触发在目标对象本身注册的事件处理程序,也称正常事件派发阶段;

c.冒泡阶段: 从目标节点到根节点,检测每个节点是否注册了事件处理程序,如果注册了事件处理程序,并且 useCapture 为false,则调用该事件处理程序;

一般地,先判断使用addEventListener,后判断attachEvent。这是因为addEventListener能兼容IE9以上和其他绝大多数浏览器,而attachEvent只适用于IE,一般用之兼容IE6~8,而IE6~8的用户较少,因此,应优先考虑先判断addEventListener以减短JS的渲染路径,以达到绝大多数用户的更好体验。

// 事件绑定器
// 参数:target为DOM对象,event为事件名称(不带"on"),callback为接收事件处理的函数
function bindEvent(target, event, callback) {
    if(window.addEventListener) {
        return target.addEventListener(event, callback, false);
    }else if(target.attachEvent) {
        return target.attachEvent("on"+event, function() {
            callback.apply(target);
        });
    }
}

四. 调用

在这之前,我已将使盒子变色的方法用changeColor()封装,并将构建了一个事件绑定器bindEvent()。

上面折腾了辣么多都还木有见到效果,别急~

最后当然是优雅的一键调用啦。哈哈哈哈~o(^▽^)o~

    var colorBox = getId("colorBox");
    bindEvent(colorBox, "click", changeColor, false);

如果页面中还有其他盒子需要变色,只需要优雅地调用一下bindEvent()即可完成扩展。

五. 性能优化

1 . 可以使用自执行函数将代码块进行封装并创建伪命名空间,只要把自己所有的函数、对象和变量都写在这个闭包函数内,那么外部就不能访问,除非你允许。

2 . 在事件绑定中,古哥建议将事件绑定器用变量存储,那样每次调用事件绑定器时就不再需要重复判断。古哥写的事件绑定器代码如下:

var w3c = document.dispatchEvent;
var bind = w3c?function(target,type,handler,phrase){
    target.addEventListener(type,handler,!!phrase);
} : function(target,type,handler){
    target.attachEvent && target.attachEvent("on"+type,function(){
        handler.apply(target);
    })
}
bind(box,"click",handleClick);

效果

本文作者:子匠_Zijor,转载请注明出处:http://www.dengzhr.com/js/424