JavaScript 进阶知识 - 特效篇(二)

9. 三大系列

本篇一开始咱们已经学了三大系列中的 offset系列,三大系列分别是 offset系列、 scroll系列、 client系列。学习这些有什么用呢?在后面的特效案例中,会大量的使用到获取元素的宽度、获取元素内部的宽度、获取元素距离顶部的距离等。这时候就须要用到三大系列,下面为你们一一讲解三大系列的用法。

9.1 offset 系列

第一章已经讲过了,详见第一章。

9.2 scroll 系列

scroll 是用来获取盒子内容的大小和位置。 scroll家族有: scrollWidthscrollHeightscrollLeftscrollTop

一、onscroll 事件html

前面DOM的时候,咱们知道了触发事件,这里讲下onscroll事件。

对于有滚动条的盒子,可使用onscroll注册滚动事件,每滚动一像素,就会触发该事件。ajax

示例代码: [31-scroll系列-onscroll事件.html]正则表达式

<!-- 样式部分 -->
<style>
    #box {
        width: 300px;
        height: 300px;
        border: 2px solid salmon;
        margin: 100px auto;
        /* 当内容超出盒子大小的时候 自动生成滚动条 */
        overflow: auto;
    }
</style>

<!-- html 部分 -->
<div id="box">
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容
                            ...
                            ...
                            ...
    我是内容我是内容我是内容我是内容我是内容我是内容我是内容
</div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    box.onscroll = function() {
        console.log("滚了!滚了");
    }
</script>

效果图:编程

image

二、scrollWidth 和 scrollHeightsegmentfault

scrollWidthscrollHeight是盒子内容的真实的宽度和高度。与和盒子大小无关,仅仅与盒子的内容有关系,不包括 bordermargin,包括 padding
scrollWidth = padding + width;

// 若是盒子里面的内容超出盒子高度的时候,这里的scrollHeight获取的就是内容的高度了
scrollHeight = padding + height;

示例代码: [32-scroll系列-scrollWidth&scrollHeight.html]数组

<!-- 样式部分 -->
<style>
    #box {
        width: 100px;
        height: 100px;
        border: 10px solid salmon;
        margin: 50px;
        padding: 10px;
    }
</style>

<!-- html 部分 -->
<div id="box">
    杨柳青青江水平,闻郎江上踏歌声。东边日出西边雨,道是无晴却有晴。
    杨柳青青江水平,闻郎江上踏歌声。东边日出西边雨,道是无晴却有晴。
</div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    console.log(box.scrollWidth);    // 120
    console.log(box.scrollHeight);   // 241 获取的是内容的高度
</script>

效果图: 浏览器

image

若是盒子里面的内容超出盒子高度的时候,这里的scrollHeight获取的就是内容的高度了app

注意:框架

  • 在现代高版本浏览器中,scrollHeight,在内容没有超度盒子的状况下,获取到的高度是height+padding
  • 可是在IE8如下的时候,即便内容没有超出盒子,获取到的高度也是内容的高度。这里就不演示了,scrollHeight不多用到。

三、scrollTop 和 scrollLeftless

scrollTop是盒子内容被滚动条卷去的头部的高度。 scrollLeft是盒子内容被滚动条卷去的左侧的宽度。一般来讲, scroll系列用的最多的地方就是用来获取页面被卷去的宽度和高度,很是的经常使用。

scrollTopscrollLeft存在兼容性

示例代码: [33-scroll系列-scrollTop&scrollLeft.html]

<!-- 样式部分 -->
<style>
    body {
        height: 5000px;
    }
</style>

<!-- js 部分 -->
<script>
    // 给页面注册滚动事件
    window.onscroll = function() {
        // 滚动条滚动一次,浏览器就会获取一次被卷去的头部的高度
        // 将高度赋值给title  
        // 获取scrollTop的时候是有兼容性的:现代浏览器用的是 window.pageYOffset
        // IE678 用的是 document.documentElement.scrollTop
        document.title = window.pageYOffset || document.documentElement.scrollTop;
    }
</script>

效果图:

image

完整版封装函数: [34-scroll系列-scrollTop&scrollLeft兼容性封装.html]

function getScroll() {
    // 返回的是一个对象,调用的时候 getScroll().top 获取页面被卷去的头部的距离
    return {
        left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
        top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
    }
}

返回值是一个对象,须要得到卷去头部的距离,只须要调用getScroll.top便可。

scroll 系列图解:

image

示例代码:固定导航栏 [ 35-scroll系列-固定导航栏.html ]

  • 经过offsetHeight获取导航栏上部元素自身的高度,判断scrollTop的高度大于等于上部元素高度的时候,说明scrollTop到导航栏的位置了;
  • 到达导航栏位置,给导航栏绝对定位在页面顶部,同时由于固定定位,导航栏脱标,因此要得到导航栏下部的元素,将其margin-top设置为导航栏的高度,将位置空出来;
  • scrollTop小于上部元素高度的时候,导航栏去掉固定定位,同时将下部元素的margin-top设置为0

为何一开始不直接拿导航栏到顶部的距离跟 scrollTop 比较呢?,由于导航栏固定定位以后位置就变了,恢复原来位置时的判断就不生效了

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    html,
    body {
        width: 100%;
    }
    .header {
        height: 130px;
        background: #FBFBFB;
        font: 700 28px/130px serif;
        color: #666;
        text-align: center;
    }
    .nav {
        height: 60px;
        width: 100%;
        background: #B9E1DC;
        font: 700 24px/60px serif;
        color: #52524E;
        text-align: center;
    }
    ul {
        display: inline-block;
    }
    li {
        float: left;
        margin-left: 60px;
    }
    .content1,
    .content2,
    .content3 {
        height: 800px;
        background: #DFFCB5;
        font: 700 60px/800px serif;
        color: #52524E;
        text-align: center;
    }
    .content2 {
        background: #FFE1B6;
    }
    .content3 {
        background: #CDE3EB;
    }
    .fixed {
        position: fixed;
        top: 0;
        left: 0;
    }
</style>

<!-- html 部分 -->
<div class="header" id="header">
    顶部广告栏
</div>
<div class="nav" id="nav">
    <ul>
        <li>HOME</li>
        <li>ABOUT</li>
        <li>SERVICES</li>
        <li>TEAM</li>
        <li>CONTACT</li>
    </ul>
</div>
<div class="content1" id="con">
    内容1
</div>
<div class="content2">
    内容2
</div>
<div class="content3">
    内容3
</div>

<!-- js 部分 -->
<script>
    var header = document.getElementById('header');
    var nav = document.getElementById('nav');
    var content = document.getElementById('con');

    // 封装一个scrollTop兼容性函数
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
    }

    // 给页面注册滚动事件
    window.onscroll = function() {
        // 判断广告栏header 与 滚动的scrollTop的值
        // 当scrollTop > header高度的时候 让导航栏 nav 固定定位
        var scrollTop = getScrollTop();
        if (scrollTop >= header.offsetHeight) {
            // 样式中有的类名这里必定不要忘了加上去,不然就会被替换掉
            nav.className = "nav fixed";
            // 一旦标题栏设置了固定定位以后,就脱离标准流了,下面的内容就会顶上来,
            // 因此要手动给下面的内容添加一个margin-top,将导航栏的位置留下来
            content.style.marginTop = nav.offsetHeight + "px";
        } else {
            // 当scrollTop < header高度的时候 让导航栏 nav 恢复到原来的位置
            // nav 取消固定定位,恢复到原来的位置,因此下面内容的margin-top也要去掉
            nav.className = "nav"; // 去掉固定定位的样式,保留以前的样式
            content.style.marginTop = 0;
        }
    };
</script>

效果图:

image

示例代码:两侧跟随小广告 [ 36-offset系列-两侧跟随小广告.html ]

  • 需求:屏幕滚动多少,两侧广告缓动等距离
  • 将两张图片绝对定位在屏幕两侧的中间
  • 经过滚动事件,实时获取scrollTop的值
  • 将获取到的scrollTop的值,经过缓动动画设置给两侧图片(须要将以前top的高度加上去)
<!-- html 部分 -->
<img src="../image/两侧固定小广告/advert.jpg" alt="" id="img1">
<img src="../image/两侧固定小广告/advert.jpg" alt="" id="img2">
<div>
    内 容
    . . .
    . . .
    
</div>

<!-- js 部分-->
<script>
    window.onload = function() {
        var imgs = document.getElementsByTagName('img');
        window.onscroll = function() {
            // 获取滚动条滚动距顶部的距离距离
            var scrollTop = getScrollTop();
            // 缓动跟随
            animate(imgs[0], scrollTop + 300);
            animate(imgs[1], scrollTop + 300);

        };
        // scrollTop兼容性处理
        function getScrollTop() {
            return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
        }
        // 缓动动画
        function animate(element, target) {
            clearInterval(element.timer);
            element.timer = setInterval(function() {
                var leader = element.offsetTop;
                var step = (target - leader) / 20;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                leader += step;
                element.style.top = leader + 'px';
                if (Math.abs(target - leader) < Math.abs(step)) {
                    element.style.top = target + "px";
                    clearInterval(element.timer);
                }
            }, 15);
        }
    }
</script>

效果图:

image

示例代码:返回顶部 [ 37-offset系列-返回顶部.html ]

window.scrollTo(0, 0);让滚动条回到 (0,0)位置。这是回到顶部的主要原理
  • 注册滚动条滚动事件,实时获取scrollTop的位置,判断当它距离大于等于800的时候,让回到顶部的按钮,缓动的显示出来。当scrollTop位置小于800的时候,让回到顶部的按钮,缓动的隐藏起来。
  • 给回到顶部按钮注册点击事件,当点击的时候,页面缓动的回到顶部。这里就要用到刚刚提到的知识点:window.scrollTo()
  • 从新建立一个缓动动画框架,目标位置target就是scrollTo(0,target),因此,target的值为0
  • 实现原理与最基本的缓动框架基本同样,只是将设置的值改成:window.scrollTo(0,leader);此时的leader仍是一个未知数,leader其实就是当前滚动条的位置,因此,在滚动事件里,只要将leader实时获取滚动条位置便可。
<!-- 样式部分 -->
<style>
    body {
        background: #FDFCE0
    }
    div {
        margin: 200px auto;
        text-align: center;
    }
    img {
        width: 50px;
        height: 50px;
        background: url(../image/返回顶部/top.png);
        cursor: pointer;
        position: fixed;
        right: 50px;
        opacity: 0;
        bottom: 50px;
    }
</style>

<!-- html 部分 -->
<div>
    ...
    内容
    ...
</div>

<img src="../image/返回顶部/top.png" alt="" id="top">

<!-- js 部分 -->
<script src="../js/slow-animate-styles.js"></script>
<script>
    var img = document.getElementsByTagName('img')[0];
    var scrollTop;
    window.onscroll = function() {
        scrollTop = getScrollTop();
        // 当滚动条位置大于等于800 的时候,让回到顶部图标缓动的显示出来
        if (scrollTop >= 800) {
            slowAnimateStyles(img, {
                opacity: 100,

            });
            img.style.display = "block";
        } else {
            // 当位置小于800 的时候,回到顶部图标缓动的隐藏起来
            slowAnimateStyles(img, {
                opacity: 0,
            }, function() {
                img.style.display = "none";
            });

            // 在这里获取leader的位置
            leader = getScrollTop();
        }
    };
    // 点击img的时候,让滚动条回到顶部,这里有个知识点:window.scrollTo(0,0); 滚动条回到顶部
    // 须要单首创建一个缓动动画
    var timer = null;
    var target = 0; // 目标位置为0 即:window.scrollTo(0,target)
    var leader = 0; // 初始化leader
    img.onclick = function() {
        clearInterval(timer);
        timer = setInterval(function() {
            var step = (target - leader) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;
            // 此时的leader仍是一个未知数,咱们须要获取到当前滚动条的位置,而后赋值给leader,
            // 而且这个位置应该是实时变化的,咱们只须要在上面的滚动事件里设置下leader便可
            window.scrollTo(0, leader);
            if (leader === 0) {
                clearInterval(timer);
            }
        }, 15);
    }

    // scrollTop兼容性处理
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
    }
</script>

效果图:

image

示例代码:楼层跳跃 [ 38-offset系列-楼层跳跃.html ]

  • 其实这里的案例跟上面的返回顶部很相似,一样的运用到的是window.scrollTo()
  • 页面布局,背景继承body,和html100%,将背景的索引与左边导航栏绑定
  • 建立一个缓动动画框架,目标距离target,就是当前索引背景距离顶部的距离,leader就是滚动条此时的位置
<!-- html 部分 -->
<ul>
    <li>鞋子区域</li>
    <li>袜子区域</li>
    <li>裤子区域</li>
    <li>裙子区域</li>
    <li>帽子区域</li>
</ul>
<ol>
    <li>鞋子</li>
    <li>袜子</li>
    <li>裤子</li>
    <li>裙子</li>
    <li>帽子</li>
</ol>

<!-- js 部分 -->
<script>
    var colorArr = ["#B7F5DE", "#FFE9E3", "#CBF078", "#7CDFFF", "#F59292"];
    var ul = document.getElementsByTagName('ul')[0];
    var ol = document.getElementsByTagName('ol')[0];
    var ulLis = ul.getElementsByTagName('li');
    var olLis = ol.getElementsByTagName('li');
    var target = 0,
        leader = 0,
        timer = null;

    for (var i = 0; i < colorArr.length; i++) {
        // 动态设置背景颜色
        ulLis[i].style.background = colorArr[i];
        olLis[i].style.background = colorArr[i];
        // 将olLis属性绑定索引值
        olLis[i].index = i;
        olLis[i].onclick = function() {
            // 点击索引,获取ul下的当前li距离顶部的距离
            target = ulLis[this.index].offsetTop;
            clearInterval(timer);
            timer = setInterval(function() {
                var step = (target - leader) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                leader += step;
                // 滚动条y方向到达leader位置
                window.scrollTo(0, leader);
                // 判断,清除定时器
                if (Math.abs(target - leader) <= Math.abs(step)) {
                    window.scrollTo(0, target);
                    clearInterval(timer);
                }
            }, 15);
        }
    }
    // 滚动事件,实时获取滚动条的位置
    window.onscroll = function() {
        // 实时获取leader的值
        leader = getScrollTop();
    }


    // scrollTop兼容性处理
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
    }
</script>

效果图:

image

9.3 client 系列

client家族用于获取盒子可视区的大小。 client家族有 clientWidthclientHeightclientLeftclientTop

一、clientWidth 和 clientHeight

  • clientWidth :获取网页可视区域宽度 ;clientHeight :获取网页可视区域高度;
  • 调用者不一样,意义不一样:

    • 盒子调用,指盒子自己;
    • html/body调用:可视区域大小
  • 不包括bordermargin

图解clientWidth和clientHeight:

image

clientWidth 和 clientHeight 兼容性封装:

function getClient() {
    return {
        width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0,
        height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0
    };
}

onresize事件:

onresize事件会在窗口被调整大小的时候发生。
window.onresize = function(){
    //事件处理程序
}

示例代码:模仿响应式布局 [ 39-client系列-模拟响应式.html ]

// 页面一进来的时候就执行一次,肯定浏览器可视区域的宽度
responsive();
// 浏览器窗口调整触发事件
window.onresize = function() {
    responsive();
};

// 获取浏览器宽度
function responsive() {
    var pageWidth = getClientWidth();
    if (pageWidth >= 960) {
        //说明是pc
        document.body.style.backgroundColor = "#B7F5DE";
    } else if (pageWidth >= 640) {
        //说明是平板
        document.body.style.backgroundColor = "#CBF078";
    } else {
        // 说明是手机
        document.body.style.backgroundColor = "#F59292";
    }

}

// clientWidth 兼容性处理
function getClientWidth() {
    return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0;
}

效果图:

image

二、clientX 和 clientY

  • clientX:鼠标距离可视区域左侧距离(event调用) event:事件对象,下面会讲
  • clientY:鼠标距离可视区域上侧距离(event调用)

事件对象的时候,单独讲解

三、clientTop 和 clientLeft

  • clientTop:盒子的上部border的宽度
  • clientleft:盒子的左部border的宽度

用的不多不多,基本不会用到

9.4 screen 系列

clientWidth 获取的实际上是浏览器窗口的宽度,想要获取用户显示的分辨率怎么办呢?

获取用户显示器分辨率有专门的方法: window.screen.widthwindow.screen.Height

示例代码:获取显示器分辨率 [ 40-screen系列-获取显示器分辨率.html ]

document.write("屏幕分辨率为:" + window.screen.width + "*" + window.screen.height);

效果图:

1280*720 分辨率的状况下:

image

1920*1080 分辨率的状况下:

image

9.5 三大系列的区别

图解三大系列区别:

image

width 和 height:

  • clientWidth/clientHeight

    • clientWidth = width + padding;
    • clientHeight = height + padding;
  • offsetWidth/offsetHeight

    • offsetWidth = width + padding + border;
    • offsetHeight = heigth + padding + border;
  • scrollWidth/scrollHeight

    • scrollWidth = 内容宽度(不包含border);
    • scrollHeight = 内容高度(不包含border);

top 和 left:

  • offsetTop/offsetLeft

    • 调用者:任意元素。(盒子为主)
    • 做用 :获取距离父系盒子中带有定位的距离。
  • scrollTop/scrollLeft:(盒子也能够调用,必须有滚动条)

    • 调用者:document.body.scrollTop/.....(window)
    • 做用:浏览器没法显示的部分(被卷去的部分)。
  • clientY/clientX:(clientTop/clientLeft 值是border)

    • 调用者:event.clientX(event)
    • 做用:鼠标距离浏览器可视区域的距离(左、上)。

10. 事件对象

10.1 事件对象的概述

在触发某个事件的时候,都会产生一个事件对象 Event,这个对象中包含全部与事件相关的一些信息,包括触发事件的元素,事件的类型以及其余与事件相关的信息。

好比:

  • 鼠标事件触发时,事件对象中会包含鼠标的位置信息。
  • 键盘事件触发时,事件对象中会包含按下的键相关的信息。

10.2 获取事件对象

既然事件对象中存储了这么多的信息,咱们首先须要作的就是获取到这个事件对象。获取事件对象的时候,存在浏览器的兼容问题。

现代浏览器:

获取事件对象很是的简单,只须要在注册事件的时候,指定一个形参便可。这个形参就是咱们想要获取到的事件对象。

btn.onclick = function(event){
    // event就是事件对象,里面包含了事件触发时的一些信息。
    // 触发事件的时候,事件是由浏览器调用,生成一个事件对象,里面包含了一些信息,当成实参传递进来了。
    console.log(event);
}

IE678:

获取事件对象则是另外一种方式,在事件里面,经过window.event来获取事件对象

btn.onclick = function(){
    // IE678经过window.event获取事件对象
    // IE678浏览器在触发的事件的时候,生成一个事件对象,可是呢,并无当成实参传过来。会给window.event这个属性。
    var event = window.event;
    console.log(event);
}

兼容性封装: [ 41-事件对象Event兼容性.html ]

btn.onclick = function(event){
    //只要用到了事件对象,就要记得处理浏览器兼容性
    event = event || window.event;
}

10.3 事件对象的经常使用属性

事件对象中有不少不少的属性,可是不少属性并不经常使用。咱们常常用到的是鼠标位置信息 和键盘码 相关的信息。

打印event对象咱们能够看到以下信息:

image

咱们能够看到一个鼠标按下的时候,它的事件对象里面有这么多属性,可是最经常使用的也就是鼠标位置信息和键盘码相关的信息。

记录了鼠标位置信息的相关属性:

  • screenXscreenY:光标相对于屏幕左上角的水平位置与垂直位置。
  • clientXclientY:光标相对于可视区左上角的水平位置和垂直位置。
  • pageXpageY:光标相对于网页(文档document)左上角的水平位置与垂直位置(推荐使用)

[ 42-事件对象-鼠标三种获取位置的属性.html ]

document.onclick = function(e) {
    var e = e || window.event;
    //获取鼠标的位置,相对的是可视区最左上角的点。(忽略滚动的距离)
    console.log("client(" + e.clientX + "," + e.clientY + ")");

    //获取鼠标的位置,相对的页面最左上角的位置 (计算滚动的距离)
    console.log("page(" + e.pageX + "," + e.pageY + ")");

    //获取鼠标的位置,相对的是屏幕最左上角的那个点
    console.log("screen(" + e.screenX + "," + e.screenY + ")");
}

图解:

image

记录了键盘码的属性:

  • event.keyCode:键盘按下的那个键的键盘码

10.4 pageX与pageY的兼容性

在鼠标事件中,记录鼠标位置信息的属性有不少,使用最多的仍是 pageXpageY这两个属性,可是 pageXpageY存在浏览器兼容性问题。

在现代浏览器中: 直接经过事件对象就能够得到pageXpageY

document.onclick = function (event) {
    event = event || window.event;
    console.log(event.pageX+","+event.pageY);
}

在IE678中: 并无pageXpageY,可是咱们能够经过scrollTop + clientY的方式进行计算来得到pageY

document.onclick = function (event) {
    event = event || window.event;
    // 在IE678中使用document.documentElement.scrollTop就能够获取到scrollTop的值
    alert(event.clientY + document.documentElement.scrollTop);
}

pageX与pageY的兼容性封装:

function getPage(event) {
    return {
        //在IE678中使用document.documentElement.scrollLeft就能够获取到scrollLeft的值
        x:event.pageX || event.clientX + document.documentElement.scrollLeft,
        y:event.pageY || event.clientY + document.documentElement.scrollTop
    }
}

调用时:

getPage(event).x;
getPage(event).y;

示例代码:兼容性封装测试 [ 43-事件对象-pageX&PageY兼容性处理.html ]

<!-- 样式部分 -->
<style>
    body {
        height: 5000px;
    }
</style>

<!-- js 部分 -->
<script>
    document.onclick = function(event) {
        event = event || window.event;
        alert("当前坐标为(" + getPage(event).x + "," + getPage(event).y + ")");
    }

    function getPage(e) {
        return {
            x: e.pageX || e.clientX + document.documentElement.scrollLeft,
            y: e.pageY || e.clientY + document.documentElement.scrollTop
        }
    }
</script>

10.5 案例:鼠标跟随

  • 鼠标跟随,指的就是,鼠标后面有一张图片,会在页面中一直跟随鼠标
  • 经过事件对象的属性,咱们知道了有三种方法获取鼠标的位置信息,咱们只要把鼠标的位置,赋值给后面跟随图片的位置,就能够实现图片一直跟随鼠标移动了
  • 咱们知道clientX/YscreenX/YpageX/Y,均可以获取鼠标的位置,可是各有优劣,咱们先使用pageX/Y获取,上面咱们已经处理pageX/Y的兼容性了,因此这里直接使用
  • 咱们只需将得到的鼠标位置,赋值给定位后的图片,将图片绝对定位,再给页面注册鼠标移动事件,图片就会一直跟着鼠标移动。

[ 44-事件对象-跟随鼠标移动.html ]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    body {
        height: 5000px;
    }
    #follow {
        position: absolute;
        width: 160px;
    }
</style>

<!-- html 部分 -->
<img src="../image/鼠标跟随/2.gif" alt="" id="follow">

<!-- js 部分 -->
<script>
var follow = document.getElementById("follow");
//给document注册一个鼠标移动事件
document.onmousemove = function(e) {
    e = e || window.event;
    console.log(e.clientX + "   " + e.clientY);
    follow.style.left = getPage(e).x + "px";
    follow.style.top = getPage(e).y + "px";
}

function getPage(e) {
    return {
        x: e.pageX || e.clientX + document.documentElement.scrollLeft,
        y: e.pageY || e.clientY + document.documentElement.scrollTop,
    }
}

效果图:

image

经过效果图咱们能够发现,绝对定位时,当鼠标移到最右边的时候,图片会撑大浏览器自动生成滚动条,那怎么办呢?

鼠标跟随优化版 [ 45-事件对象-跟随鼠标移动优化版.html ]
只要将图片固定定位,而后经过clientX/Y,获取可视区的位置,将它的值赋值给图片就好了

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    body {
        height: 5000px;
    }
    #follow {
        position: fixed;
        width: 160px;
    }
</style>

<!-- html 部分 -->
<img src="../image/鼠标跟随/2.gif" alt="" id="follow">

<!-- js 部分 -->
<script>
    var follow = document.getElementById("follow");
    //给document注册一个鼠标移动事件
    document.onmousemove = function(e) {
        e = e || window.event;
        console.log(e.clientX + "   " + e.clientY);
        follow.style.left = e.clientX + "px";
        follow.style.top = e.clientY + "px";
    }
</script>

效果图:

image

10.6 案例:拖拽效果

一、获取鼠标在盒子中的位置

当在盒子里面点击鼠标的时候,怎么得到这个鼠标在盒子中的位置呢?

没有直接的方法可以获取,可是咱们能够经过:

获取鼠标的位置 - 盒子距离顶部和左边的距离 = 鼠标在盒子里面的距离

[ 46-事件对象-获取鼠标在盒子中的位置.html ]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    #box {
        width: 150px;
        height: 150px;
        background: #b7f5de;
        margin: 200px;
    }
</style>

<!-- html 部分 -->
<div id="box"></div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');

    box.onclick = function(e) {
        var e = e || window.event;
        // 没有直接的方法可以获取,可是咱们能够经过:
        // 获取鼠标的位置 - 盒子距离顶部和左边的距离 = 鼠标在盒子里面的距离
        var x = getPage(e).x - box.offsetLeft;
        var y = getPage(e).y - box.offsetTop;
        console.log("当前位置坐标:(" + x + "," + y + ")");
    }

    function getPage(e) {
        return {
            x: e.pageX || e.clientX + document.documentElement.scrollLeft,
            y: e.pageY || e.clientY + document.documentElement.scrollTop
        }
    }
</script>

效果图:

image

二、拖拽效果

拖拽效果在网页很常见,好比一个注册框,弹出来的时候,你能够拖动它的位置。

新事件:

  • onmousedown :当鼠标按下的时候触发
  • onmouseup :当鼠标弹起的时候触发

实现思路:

  • 给盒子注册按下鼠标(onmousedown)事件,获取鼠标在盒子里的位置;
  • 而后在里面注册页面移动鼠标(onmousemove)事件,鼠标移动时,将此时的鼠标距浏览器的距离减去鼠标在盒子中的距离后,赋值给盒子的top/left
  • 给页面注册松开鼠标(onmouseup)事件,盒子应该停在那个位置,因此清除移动事件。

[ 47-事件对象-拖拽效果.html ]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    body {
        height: 4000px;
    }
    #box {
        width: 150px;
        height: 150px;
        background: #B7F5DE;
        position: absolute;
    }
</style>

<!-- html 部分 -->
<div id="box"></div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');

    // 鼠标按下
    box.onmousedown = function(event) {
        event = event || window.event;
        // 记录按下的鼠标的位置
        var x = getPage(event).x - box.offsetLeft;
        var y = getPage(event).y - box.offsetTop;
        // 按下的时候才触发鼠标移动事件
        document.onmousemove = function(e) {
            // 鼠标点击的时候应该减去鼠标按下时在盒子中的位置
            box.style.left = getPage(e).x - x + "px";
            box.style.top = getPage(e).y - y + "px";
        }
    }

    // 鼠标松开
    // 这里为何不给盒子注册鼠标松开事件呢? 由于一旦有延迟,鼠标不在盒子上松开的时候,move事件就清除不掉了
    // 因此直接给页面注册鼠标松开事件,只要鼠标松开,就清除move事件
    document.onmouseup = function() {
        // 上面注册的移动事件会一直触发,因此在鼠标松开的时候,咱们应该将移动事件移除掉
        document.onmousemove = null;
    }

    // 获取事件对象里的pageX/Y属性
    function getPage(e) {
        return {
            x: e.pageX || e.clientX + document.documentElement.scrollLeft,
            y: e.pageY || e.clientY + document.documentElement.scrollTop
        }
    }
</script>

效果图:

image

注意:

  • 拖拽的时候,可能里面会有文字,当移动的时候,不当心获取文字焦点的时候,就不能清除鼠标移动事件了。

解决方法:
清除选中的文字

window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();

10.7 案例:放大镜

放大镜在开发中是一个很常见的特效,可是全部的放大镜的实现效果都是同样。

图解放大镜原理:

image

实现思路:

  • 当鼠标通过 smallBox 的时候,显示 maskbigBox
  • 当鼠标离开 smallBox 的时候,隐藏 maskbigBox
  • 获取鼠标在 smallBox 里面的位置;
  • 得到鼠标在 smallBox 里面的位置后,要减去 mask 一半的宽高,不然鼠标不在 mask 中间显示;
  • 判断x的值限定 mask 的位置 :

    • mask 在小盒子里面可以移动最大的宽度和高度 0
    • mask 在小盒子里面可以移动最大的宽度 = smallBox的宽度 - mask的宽度
  • 设定 mask 的位置;
  • 让大图片等比例的跟着动 :

    • bigImg 可以移动的距离 / mask 能移动的距离 = 大图片移动的距离 / mask移动的距离

思路图解:

image

示例代码: [ 48-事件对象-放大镜效果.html ]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    #box {
        width: 350px;
        height: 350px;
        margin: 100px;
        border: 1px solid #ccc;
        position: relative;
    }
    #bigBox {
        width: 400px;
        height: 400px;
        position: absolute;
        top: 0;
        left: 360px;
        border: 1px solid #ccc;
        overflow: hidden;
        display: none;
    }
    .mask {
        width: 175px;
        height: 175px;
        background-image: url(../image/放大镜/1.png);
        position: absolute;
        top: 1px;
        left: 1px;
        cursor: move;
        display: none;
    }
    #smallBox {
        position: relative;
    }
    #box img {
        vertical-align: top;
    }
    #bigBox img {
        position: absolute;
    }
</style>

<!-- html 部分 -->
<div id="box">
    <div id="smallBox">
        <img src="../image/放大镜/img.jpg" width="350" alt="">
        <div id="mask" class="mask"></div>
    </div>
    <div id="bigBox">
        <img src="../image/放大镜/img.jpg" width="800" id="bigImg" alt="" />
    </div>
</div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    var smallBox = document.getElementById('smallBox');
    var bigBox = document.getElementById('bigBox');
    var mask = document.getElementById('mask');
    var bigImg = document.getElementById('bigImg');

    // 当鼠标 通过smallBox的时候,显示 mask 和 bigBox
    smallBox.onmouseover = function() {
        mask.style.display = "block";
        bigBox.style.display = "block";
    };
    // 当鼠标 离开smallBox的时候,隐藏 mask 和 bigBox
    smallBox.onmouseout = function() {
        mask.style.display = "none";
        bigBox.style.display = "none";
    };
    // 鼠标在smallBox里面移动,移动 mask 和 bigImg
    smallBox.onmousemove = function(e) {
        var e = e || window.event;
        // mask 跟着鼠标移动
        // 1- 获取鼠标在smallBox里面的位置
        var spaceX = getPage(e).x - box.offsetLeft;
        var spaceY = getPage(e).y - box.offsetTop;

        // 2- 得到鼠标在smallBox里面的位置后,要减去mask一半的宽高,不然鼠标不在mask中间显示
        var x = spaceX - mask.offsetWidth / 2;
        var y = spaceY - mask.offsetHeight / 2;

        // 3- 判断x的值 限定 mask的位置 
        // mask 在小盒子里面可以移动最大的宽度和高度 0 
        if (x <= 0) {
            x = 0;
        }
        if (y <= 0) {
            y = 0;
        }
        // mask 在小盒子里面可以移动最大的宽度 = smallBox的宽度 - mask的宽度
        var maskMaxX = smallBox.offsetWidth - mask.offsetWidth;
        var maskMaxY = smallBox.offsetHeight - mask.offsetHeight;
        if (x >= maskMaxX) {
            x = maskMaxX;
        }
        if (y >= maskMaxY) {
            y = maskMaxY;
        }

        // 4- 设定mask的位置
        mask.style.left = x + "px";
        mask.style.top = y + "px";

        // 5- 让大图片等比例的跟着动   
        // bigImg 可以移动的距离 / mask 能移动的距离 = 大图片移动的距离 / mask移动的距离
        // rate :比例
        var xRate = (bigImg.offsetWidth - bigBox.offsetWidth) / maskMaxX;
        bigImg.style.left = -xRate * x + "px";
        var yRate = (bigImg.offsetHeight - bigBox.offsetHeight) / maskMaxY;
        bigImg.style.top = -yRate * y + "px";
    };

    // pageX/Y 兼容性处理
    function getPage(e) {
        return {
            x: e.pageX || e.clientX + document.documentElement.scrollLeft,
            y: e.pageY || e.clientY + document.documentElement.scrollTop
        }
    }
</script>

效果图:

image

11. 注册事件

前面咱们已经知道了许多触发事件的名称,可是咱们只知道了一种注册事件的方式,就是" on + 事件名称",下面会为你们再介绍一种注册事件的方式: addEventListener

11.1 on + 事件名称 方式

onclickonmouseover这种 on+ 事件名称的方式注册事件几乎全部的浏览器都支持。

注册事件:

box.onclick = function(){
    //事件处理程序    
}

移除事件:

box.onclick = null;

on + 事件名称注册事件的缺点:

同一个元素同一类型的事件,只能注册一个,若是注册了多个,会出现覆盖问题。

document.onclick = function(){
    console.log("呵呵");
}

document.onclick = function(){
    console.log("哈哈");   // 最后打印的是 "哈哈",呵呵会被覆盖掉
}

11.2 addEventListener 方式

现代浏览器支持的注册事件的新方式,这种方式注册的事件不会出现覆盖问题。之后在手机端就用这种注册事件的方式。

一、addEventListener 注册事件的语法:

// add:添加   Event:事件   Listener:监听器
// 三个参数:
//      1. type:事件类型  "click","mouseover"... 不要再加 on了
//      2. 函数:事件触发的时候要执行的程序
//      3. useCapture(是否使用事件捕获) :true & false 默认是false
document.addEventListener(type,function(){
    // 事件处理程序
},useCapture);

以前咱们说过window.onload,只能注册一个就是这个缘由,由于"on +"注册方式会覆盖。因此若是真的须要执行两个window.onload事件的时候,咱们就可使用addEventListener注册:

window.addEventListener("load",function(){
    // 预加载函数 1
});

window.addEventListener("load",function(){
    // 预加载函数 2
});

示例代码: [ 49-注册事件-addEventListener.html ]

// 给页面注册点击事件后,会同时打印 "呵呵呵","哈哈哈"
document.addEventListener("click", function() {
    console.log("呵呵呵");
});
document.addEventListener("click", function() {
    console.log("哈哈哈");
});

二、removeEventListener 移除事件的语法:

// remove:移除   Event:事件   Listener:监听器
// 三个参数:
//      1. type:事件类型  "click","mouseover"
//      2. 函数名:要移除的那个函数
//      3. useCapture(是否使用事件捕获) :true & false 默认是false
document.addEventListener(type,fn,useCapture);

注意:

要想一个事件可以被移除,在它注册事件的时候,执行函数必需要有函数名,不能是匿名函数。由于移除事件的时候,就是移除的这个函数名。

示例代码: [ 50-移除事件-removeEventListener.html ]

// 第二个点击事件就被移除了
document.addEventListener("click", fn1);
document.addEventListener("click", fn2);

function fn1() {
    console.log("呵呵呵");
};

function fn2() {
    console.log("哈哈哈");
}

// 移除第二个点击事件
document.removeEventListener("click", fn2);

三、IE678兼容性问题:

IE678不支持 addEventListenerremoveEventListen两个方法,可是支持 attachEventdetachEvnet

attachEvent注册事件的语法:

// attach :附上;系上;贴上 
// 参数:
//     1. type:事件类型   须要加上on "onclick","onmouseenter"...
//     2. 函数fn:须要执行的那个事件函数
attachEvent(type, function(){
    // 事件处理程序
});

attach注册时间的时候,事件类型要加上on,没有为何,IE就这样

detachEvent的用法:

// detach :脱离
// 参数:
//     1. type:事件类型   须要加上on "onclick","onmouseenter"...
//     2. 函数名: 须要执行的那个事件函数名
detachEvent(type, fn);

示例代码: [ 51-注册事件-IE678方法.html ]

// IE678 下运行
document.attachEvent("onclick", fn1);
document.attachEvent("onclick", fn2);

function fn1() {
    alert("123");
};

function fn2() {
    alert("456");
};

// 移除第一个注册事件
document.detachEvent("onclick", fn1);

四、兼容性处理:

注册事件的新方式的解决了事件覆盖的问题,可是存在浏览器兼容性问题,所以能够进行兼容性封装。
// 添加事件兼容性封装
function addEvent(element, type, fn) {
    // 能力检测
    if (element.addEventListener) {
        element.addEventListener(type, fn);
    } else if (element.attachEvent) {
        element.attachEvent("on" + type, fn);
    } else {
        //若是都不行,那就用on方式
        element["on" + type] = fn;
    }
};

//移除事件兼容性封装
function removeEvent(element, type, fn) {
    if (element.removeEventListener) {
        element.removeEventListener(type, fn);
    } else if (element.detachEvent) {
        element.detachEvent("on" + type, fn);
    } else {
        element["on" + type] = null;
    }
}

示例代码: [ 52-注册事件-封装兼容性.html ]

// 添加事件兼容性封装
function addEvent(element, type, fn) {
    // 能力检测
    if (element.addEventListener) {
        element.addEventListener(type, fn);
    } else if (element.attachEvent) {
        element.attachEvent("on" + type, fn);
    } else {
        //若是都不行,那就用on方式
        element["on" + type] = fn;
    }
};

//移除事件兼容性封装
function removeEvent(element, type, fn) {
    if (element.removeEventListener) {
        element.removeEventListener(type, fn);
    } else if (element.detachEvent) {
        element.detachEvent("on" + type, fn);
    } else {
        element["on" + type] = null;
    }
}

function fn1() {
    alert("呵呵");
}

function fn2() {
    alert("哈哈");
}

addEvent(document, "click", fn1);
addEvent(document, "click", fn2);
removeEvent(document, "click", fn1);

12. 事件冒泡和事件捕获

事件冒泡和事件捕获其实能够理解成同样东西,就是当父级元素和子元素都具备点击事件的时候,点击触发子级元素的时候父级元素也会被触发。事件冒泡是 IE678在处理事件间机制的一种说法,它执行的顺序是由内向外的,就是从子元素一直到 window。 事件捕获是火狐在处理机制时的一种说法,它执行的顺序是由外向内的,就是 window一直到子元素。

图解事件冒泡和事件捕获:

image

12.1 事件冒泡

当一个元素的事件被触发时,一样的事件将会在该元素的全部祖先元素中依次被触发。这一过程被称为事件冒泡。

说白了就是:当父元素和子元素都设置了点击事件的时候,触发子盒子点击事件的时候,父盒子的点击事件也会被执行。

image

示例代码: [ 53-事件冒泡.html ]

<!-- 样式部分 -->
<style>
        #big-box {
            width: 500px;
            height: 500px;
            margin: 100px auto;
            vertical-align: middle;
            text-align: center;
            border: 1px solid transparent;
            background-color: aquamarine;
        }
        
        #box {
            width: 300px;
            height: 300px;
            margin-top: 100px;
            display: inline-block;
            background-color: darkorange;
        }
</style>

<!-- html 部分 -->
<div id="big-box">
    <div id="box"></div>
</div>

<!-- js 部分 -->
<script>
    var bigBox = document.getElementById('big-box');
    var box = document.getElementById('box'); 
    document.onclick = function() {
        document.body.style.background = "#000";
    }
    bigBox.onclick = function() {
        bigBox.style.background = "fuchsia";
    }
    box.onclick = function() {
        // 当咱们点击box的时候,bigBox、body的点击事件 也会被触发
        box.style.background = "lightgreen";
    }
    </script>

效果图:

image

咱们会发现,当点击中间小盒子的时候,他的父级元素,只要有点击事件的,都被触发了,这就是事件冒泡。

12.2 阻止事件冒泡

正常状况下,咱们确定不想,点击子元素触发事件的时候,父元素事件也跟着触发,因此咱们就要知道一个知识点:阻止事件冒泡。在阻止事件冒泡中是存在兼容性的:

正常浏览器:

前面咱们知道了事件触发的时候,会有一个事件对象,咱们只要给事件对象加上:stopPropagation方法便可。stopPropagation方法不只能够阻止事件冒泡,还能够阻止事件委托

element.onclick = function (e) {
    e = event || window.event;
    //stop :中止  propagation:传播
    e.stopPropagation();
}

IE678浏览器:

ie是给事件对象的属性cancelBubble赋值

element.onclick = function (e) {
    e = event || window.event;   
    e.cancelBubble = true;
}

兼容性处理:

// 能力检测
element.onclick = function (e) {
    e = event || window.event;
    if(e.stopPropagation){
          e.stopPropagation();
    }else {
          e.cancelBubble = true;
    }
}

12.3 事件捕获

事件捕获( capture)是火狐浏览器提出来的, IE678不支持事件捕获(基本上,咱们都是用事件冒泡)。事件的处理将从 DOM层次的根开始,而不是从触发事件的目标元素开始,事件被从目标元素的全部祖先元素依次往下传递。

image

addEventListener第三个参数为true时,表示事件捕获:

element.addEventListener("click", function () {
    console.log("哈哈哈");
},true);

12.4 事件流

事件流就是事件的三个阶段,首先发生的是捕获阶段,而后是目标阶段,最后才是冒泡阶段,对于捕获和冒泡,咱们只能干预其中的一个,一般来讲,咱们可能会干预事件冒泡阶段,而不去干预事件捕获阶段。
  • 事件的捕获阶段
  • 事件的目标阶段
  • 事件的冒泡阶段

image

注意:

其实这三个阶段在执行是都会发生,可是冒泡和捕获只能执行一个,因此经过usecaptrue = false可让捕获阶段执行可是不触发。

12.5 键盘事件

对于鼠标事件,事件对象中有一系列的 XY记录了鼠标的位置信息。而键盘事件中,事件对象有一个 event.keyCode属性,记录了按下去的键的键盘码。 [ 54-键盘事件-键盘码.html ]
document.onkeydown = function (e) {
    // 键盘按下的时候触发的事件对象 
    console.log(e);
    // keyCode: 键盘码
    console.log(e.keyCode);
}

image

键盘码对应值:

image

常见的键盘事件:

  • onkeydown:键盘按下时触发
  • onkeyup:键盘弹起时触发

示例代码: [ 55-键盘事件-ESC键关闭遮罩层弹出框.html ]

// 点击登录按钮
var btn = document.getElementById('btn');
// 登录框
var login = document.getElementById('login');
// 遮罩层
var bg = document.getElementById('bg');
btn.addEventListener("click", function() {
    login.style.display = "block";
    bg.style.display = "block";
});
document.addEventListener("keyup", function(e) {
    e = e || window.event;
    // ESC 键的键盘码是27
    if (e.keyCode == 27) {
        login.style.display = "none";
        bg.style.display = "none";
    }
});

效果图:

image

12.6 案例:弹幕效果

咱们都看过直播,都知道弹幕的效果,下面咱们就模拟直播中的弹幕作个小案例。

实现步骤:

  • 获取输入框的的 value 值;并生成 span 标签
  • span 标签添加到 页面中,随机颜色 随机高度 span动画从右向左
  • 到达最左边的时候删除 span 标签(不删除会随着输入的内容愈来愈多影响性能)

示例代码:

<style>
    html,
    body {
        margin: 0px;
        padding: 0px;
        width: 100%;
        height: 100%;
        font-family: "微软雅黑";
        font-size: 62.5%;
        background: #ccc;
    }
    #page {
        width: 100%;
        height: 100%;
        position: relative;
        overflow: hidden;
    }
    #import {
        width: 100%;
        height: 60px;
        background: #666;
        position: fixed;
        bottom: 0px;
    }
    #content {
        display: inline-block;
        width: 430px;
        height: 40px;
        position: absolute;
        left: 0px;
        right: 0px;
        top: 0px;
        bottom: 0px;
        margin: auto;
    }
    .title {
        display: inline;
        font-size: 25px;
        vertical-align: bottom;
        color: #fff;
    }
    #text {
        border: none;
        width: 300px;
        height: 30px;
        border-radius: 5px;
        font-size: 15px;
        padding-left: 10px;
    }
    #btn {
        width: 60px;
        height: 30px;
        background: #f90000;
        border: none;
        color: #fff;
        font-size: 15px;
    }
    span {
        width: 300px;
        height: 40px;
        position: absolute;
        overflow: hidden;
        color: #000;
        font-size: 25px;
        line-height: 37.5px;
        cursor: pointer;
        white-space: nowrap;
    }
</style>

<!-- html 部分-->
<div id="page">
    <div id="import">
        <div id="content">
            <p class="title">吐槽</p>
            <input type="text" name="" id="text" placeholder="发送弹幕,与小伙伴一块儿互动!">
            <button id="btn">发射</button>
        </div>
    </div>
</div>

<!-- js 部分 -->
<script src="../js/animate-callback.js"></script>
<script>
    var page = document.getElementById('page');
    var text = document.getElementById('text');
    var btn = document.getElementById('btn');

    // 定义一个颜色数组
    var colorArr = ['#FF895D', '#78BBE6', '#FF4273', '#00BBF0', '#7C73E6', '#EE2B47', '#F60C86', '#9870FC', '#F96D00', '#303481'];

    btn.onclick = function() {
        // 点击发射按钮的时候,要作的事情:
        // 1- 获取 input 的 value 值;并生成 span 标签
        // 2- 将 span 标签添加到 page中,随机颜色 随机高度 span动画从右向左
        // 3- 到达最左边的时候删除 span 标签(不删除会随着输入的内容愈来愈多影响性能)
        // a. 获取input的值,并清空
        var content = text.value;
        // b. 生成span标签 添加到 page中
        if (content != "" && content.trim()) {
            text.value = '';
            var span = document.createElement('span');
            page.appendChild(span);
            span.innerText = content;
            // c. 随机颜色
            var randomColor = parseInt(Math.random() * colorArr.length);
            span.style.color = colorArr[randomColor];
            // d. 随机高度  随机位置
            var randomHeight = parseInt(Math.random() * 201);
            span.style.top = randomHeight + "px";
            span.style.left = getClient().width + "px";
            // e. 动画效果
            animate(span, -300, function() {
                // f. 动画执行完成以后,回调函数中移除执行完的 span
                page.removeChild(span);
            });
        }
    };

    // text 注册键盘按下事件 当为回车按键的时候,执行发射操做
    text.onkeydown = function(e) {
        e = e || window.event;
        if (e.keyCode == 13) {
            btn.click();
        }
    }

    // 获取可视区域宽高
    function getClient() {
        return {
            width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 0,
            height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 0
        }
    }
</script>

效果图:

image

13. 瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为良莠不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。

image

一、首先瀑布流全部的图片应该保持宽度一致,高度是由内容决定。

image

左浮动的话,咱们能够看到第6个盒子直接就在第4个盒子旁边停下了,由于第4个高度最高,挡住了它左浮动的去路。第6个盒子是第2行的最后一个,因此第7个盒子只能在第3行排列了。当排到第12个盒子的时候,盒子会以第11个盒子的位置为基础左浮动(这就是第12个盒子为何没有‘跳到’第9个盒子下面的缘由),碰到第8个盒子后又被挡住了。

image

经过定位的方式是咱们实现瀑布流的最基本的原理,只要咱们动态的设置它的top值、left值,就能让它排列。

二、定位后肯定浏览器显示区域内,一行能放多少列图片盒子。

  • 获取页面的宽度
  • 获取图片盒子的宽度
  • 显示的列数 = 页面宽度/图片盒子宽度
  • column = pageWidth / itemWidth

image

三、为了美观咱们能够加上一个空隙

  • 显示的列数 = 页面宽度/(图片盒子宽度+间隙);
  • column = pageWidth / (itemWidth + gap);

image

四、 肯定列数以后,排列第一行

  • 下面还有不少图片盒子,咱们先要排列第1行,因此在for循环里就要判断一下,当i(全部图片盒子的索引) < column(显示列数)的时候,说明在第1行;
  • 知道在第1行以后,动态设置每一个图片盒子的left值就能排好第1行。
  • left = i * ( itemWidth + gap );

image

五、第1行排列好以后,获取第1行全部图片盒子的高度

  • 须要定义一个数组arr,将获取到的高度存在数组中,由于第2行排列的时候须要考虑top值,此时只能根据第1行图片盒子的高度来设置;
  • 获取图片高度的时候要注意,程序必须写在入口函数onload里面,由于图片的加载特性是:等页面都加载完以后才去请求加载,因此不写在入口函数里可能会出现高度获取不到的状况。

image

六、排列第2行

  • 获取到刚刚数组中,高度最小的那一列,将第2行的第1个图片盒子放置在它的下方;
  • 此时的left值就是高度最小列的offsetLefttop值就是:第1行高度最小列的高度(为了布局美观能够加上上下间隙gap)。
  • 记录下高度最小列的索引index,后面计算会用到;
  • 设置完成以后,会发现后面全部的图片都叠在这个高度最小列的下面,缘由就是此时的最小列高度没有改变,应该加上下面图片的高度,得出一个新高度。

image

七、改变最小列当前高度

  • 此时的这一列高度其实已经发生改变了,因此须要将新高度赋值给数组
  • 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度

image

八、触发resize事件

  • 将整个设置样式的部分封装成一个函数,在onload里面注册一个resize事件,只要页面一发生改变,就触发样式部分的代码。
  • 实时改变pageWidth的宽度,这样瀑布流就会是一个响应式的效果了

九、懒加载效果

  • 目前咱们用的是30张图片,假如一个页面中有几百张图片的时候,咱们不可能等到它都加载完再显示,全部这里引入一个懒加载的概念,咱们规定第30张为显示的最后一张图片,当滚动条滚动到30张的时候,应该加载下一批图片。
  • 即页面可视区高度+滚动条卷去的高度 = 第30图片的offsetTop;的时候加载下面的图片。

image

完整代码:

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        position: relative;
    }
    
    img {
        width: 220px;
        display: block;
    }
    
    .item {
        box-shadow: 2px 2px 2px #999;
        position: absolute;
    }
</style>

<!-- html 部分 -->
<div id="box">
    <div class="item"><img src="../image/瀑布流/001.jpg" alt=""></div>
                                .
                                .
                                .
    <div class="item"><img src="../image/瀑布流/030.jpg" alt=""></div>
</div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    var items = box.children;
    // 定义每一列之间的间隙 为10像素
    var gap = 10;

    window.onload = function() {
        // 一进来就调用一次
        waterFall();
        // 封装成一个函数
        function waterFall() {
            // 1- 肯定列数  = 页面的宽度 / 图片的宽度
            var pageWidth = getClient().width;
            var itemWidth = items[0].offsetWidth;
            var columns = parseInt(pageWidth / (itemWidth + gap));
            var arr = [];
            for (var i = 0; i < items.length; i++) {
                if (i < columns) {
                    // 2- 肯定第一行
                    items[i].style.top = 0;
                    items[i].style.left = (itemWidth + gap) * i + 'px';
                    arr.push(items[i].offsetHeight);

                } else {
                    // 其余行
                    // 3- 找到数组中最小高度  和 它的索引
                    var minHeight = arr[0];
                    var index = 0;
                    for (var j = 0; j < arr.length; j++) {
                        if (minHeight > arr[j]) {
                            minHeight = arr[j];
                            index = j;
                        }
                    }
                    // 4- 设置下一行的第一个盒子位置
                    // top值就是最小列的高度 + gap
                    items[i].style.top = arr[index] + gap + 'px';
                    // left值就是最小列距离左边的距离
                    items[i].style.left = items[index].offsetLeft + 'px';

                    // 5- 修改最小列的高度 
                    // 最小列的高度 = 当前本身的高度 + 拼接过来的高度 + 间隙的高度
                    arr[index] = arr[index] + items[i].offsetHeight + gap;
                }
            }
        }
        // 页面尺寸改变时实时触发
        window.onresize = function() {
            waterFall();
        };
        // 当加载到第30张的时候
        window.onscroll = function() {
            if (getClient().height + getScrollTop() >= items[items.length - 1].offsetTop) {
                // 模拟 ajax 获取数据    
                var datas = [
                    "../image/瀑布流/001.jpg",
                            ...
                    "../image/瀑布流/030.jpg"
                ];
                for (var i = 0; i < datas.length; i++) {
                    var div = document.createElement("div");
                    div.className = "item";
                    div.innerHTML = '<img src="' + datas[i] + '" alt="">';
                    box.appendChild(div);
                }
                waterFall();
            }

        };
    };

    // clientWidth 处理兼容性
    function getClient() {
        return {
            width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
            height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
        }
    }
    // scrollTop兼容性处理
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop;
    }
</script>

效果图:

image

14. 正则表达式

正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工做原理的早起研究,如今在编程语言中有普遍的应用,常常用于表单校验,高级搜索等。

14.1 建立正则表达式

js中的正则表达式用RegExp对象表示,能够经过RegExp()构造函数来建立RegExp对象,不过更多的是经过字面量语法来建立。

/.../正则表达式必需要有斜杠,它表示的是正则构成

构造函数的方式:

var regExp = new RegExp(/abc/); // 判断是否包含字符abc

正则字面量:/.../

var regExp = /abc/;

正则的使用:

正则表达式有一个方法:test(); 有一个返回值,是布尔类型。决定参数是否符合正则表达式

console.log(/abc/.test("abc")); // true

示例代码: [ 57-正则表达式-建立正则表达式.html ]

var reg = new RegExp(/abc/);
console.log(reg.test("abc"));   // ture
console.log(reg.test("efg"));   // false
console.log(reg.test("abcd"));  // true   只要包含abc就正确 后面会细讲
console.log(/123/.test(123));   // true

14.2 元字符

正则表达式由一些 普通字符元字符组成,普通字符包括大小写字母、数字等,而元字符则具备特殊的意义。元字符: ^...

14.3 正则内部类

一、预约义类

正则表达式中具备特殊意义的字符。
预约义类 正则形式 释义
. [^\n\r] 除了换行和回车以外的任意字符
\d [0-9] 数字字符
\D [^0-9] 非数字字符
\w [a-zA-Z0-9_] 单词字符(全部的字母数字和'_')
\W [^a-zA-Z0-9_] 非单词字符
\s [\f\r\n\t\v] 不可见字符,包含空格
\S [^\f\r\n\t\v] 可见字符

示例代码: [ 59-正则表达式-预约义类.html ]

console.log("----------------'.'---------------");
console.log(/./.test('\n'));        // false
console.log(/./.test('2s#2'))       // true

console.log("----------------'\\d'---------------");
console.log(/\d/.test(123));        // true
console.log(/\d/.test('123abc'));   // true
console.log(/\d/.test('abc'));      // false

console.log("----------------'\\D'---------------");
console.log(/\D/.test(123));        // false
console.log(/\D/.test('123abc'));   // true
console.log(/\D/.test('abc'));      // true

console.log("----------------'\\w'---------------");
console.log(/\w/.test(123));        // true
console.log(/\w/.test('123abc_'));  // true
console.log(/\w/.test(' '));        // false

console.log("----------------'\\W'---------------");
console.log(/\W/.test(123));        // false
console.log(/\W/.test('123abc_'));  // false
console.log(/\W/.test(' '));        // true

console.log("----------------'\\s'---------------");
console.log(/\s/.test(123));        // false
console.log(/\s/.test('123abc_'));  // false
console.log(/\s/.test(' '));        // true

console.log("----------------'\\S'---------------");
console.log(/\S/.test(123));        // true
console.log(/\S/.test('123abc_'));  // true
console.log(/\S/.test(' '));        // false

二、简单类 [ 60-正则表达式-简单类.html ]

/ /中什么特殊符号也不写,就是简单类

直接字符: 必须是完整的包含正则选项,只能多不能少

console.log(/levi/.test('levi'));       // true
console.log(/levi/.test('le'));         // false
console.log(/levi/.test('levi_lxh'));   // true

只要完整的包含了“levi”便可(有他就行)

加上[] 只要包含里面任何一个便可 好比/[abcd]/ => a,b,c,d只要匹配项的里面有任意一项符合就返回true

console.log(/[levi]/.test("le"));       // true
console.log(/[levi]/.test("less"));     // true
console.log(/[levi]/.test("kill"));     // true
console.log(/[levi]/.test("ss"));       // false

三、负向类

元字符 ^必须出如今中括号内,表示非、取反的意思 [^]

示例代码: [ 61-正则表达式-负向类.html ]

console.log(/[^levi]/.test("l"));           // false
console.log(/[^levi]/.test("le"));          // false
console.log(/[^levi]/.test("ec"));          // true
console.log(/[^levi]/.test("levi"));        // false
console.log(/[^levi]/.test("levi-lxh"));    // true
console.log(/[^levi]/.test("lxh"));         // true

条件项[^levi],表示不能有l,e,v, i任意组合,当匹配项小于等于条件项而且包含条件项的时候,返回false,当返回项不彻底包含条件项的时候,返回true

四、范围类:

有时候匹配的东西过多,并且类型又相同,所有输入太麻烦,咱们能够在中间加个横线 -

示例代码: [ 62-正则表达式-范围类.html ]

console.log(/[a-d]/.test("a"));     // true
console.log(/[a-d]/.test("ac123")); // true
console.log(/[a-d]/.test("efg"))    // false
console.log(/[a-d]/.test("123"))    // false

五、组合类

用中括号匹配不一样类型的单个字符串

示例代码: [ 63-正则表达式-组合类.html ]

console.log(/[a-f1-6]/.test('abs'));    // true
console.log(/[a-f1-6]/.test('12'));     // true
console.log(/[a-f1-6]/.test('sg8'));    // false

14.4 正则边界

咱们前面学习的正则只要有知足的条件的就会返回true,并不能作到精确的匹配。正则边界就是以什么开始,以什么结束,进行精确匹配。

一、以什么开始:

^元字符在 //里面的时候,表示的是必须 以...开始^在中括号 []内才表示取反、非的意思。
console.log(/^levi/.test('lxhlevi'));       // false
console.log(/^levi/.test('levilxh'));       // true
console.log(/^levi/.test('lxhlevilxh'));    // false
console.log(/^levi/.test('levilevi'));      // true

二、以什么结尾:

$ 元字符表示的是必须以...结尾
console.log(/levi$/.test('lxhlevi'));       // true
console.log(/levi$/.test('levilxh'));       // false
console.log(/levi$/.test('lxhlevilxh'));    // false
console.log(/levi$/.test('levilevi'));      // true

三、精确匹配:

^...$表示的是精确匹配,匹配项必须是 ^、$之间的内容
console.log(/^levi$/.test('lxhlevi'));      // false
console.log(/^levi$/.test('levilxh'));      // false
console.log(/^levi$/.test('lxhlevilxh'));   // false
console.log(/^levi$/.test('levilevi'));     // false
console.log(/^levi$/.test('levi'));         // true
console.log(/^\d$/.test('111'));            // false  \d表示的是0-9当中的一位数
console.log(/^\d$/.test('1'));              // true

[ 64-正则表达式-正则边界.html ]

14.5 量词

量词用来控制出现的次数,通常来讲量词和边界会一块儿使用

一、量词 *

表示可以出现 0次,或者跟多的次数, x >= 0
// 能够出现0次或者屡次  要么不出现 要么只能出现 a
console.log(/^a*$/.test('abc'));    // false
console.log(/^a*$/.test('bbb'));    // false
console.log(/^a*$/.test('aab'));    // false
console.log(/^a*$/.test('aaa'));    // true
console.log(/^a*$/.test('a'));      // true
console.log(/^a*$/.test(''));       // true

二、量词 +

表示可以出现 1次或者屡次, x >= 1
// +表示 能够出现1次或者1次以上
console.log(/^a+$/.test("a"));      //true
console.log(/^a+$/.test(""));       //false
console.log(/^a+$/.test("b"));      //false
console.log(/^a+$/.test("aa"));     //true
console.log(/^a+$/.test("aab"));    //false

三、量词 ?

表示可以出现 0次或者 1次, x=0或者 x=1
// ? 表示能够出现0次或者1次
console.log(/^a?$/.test("a"));      //true
console.log(/^a?$/.test(""));       //true
console.log(/^a?$/.test("b"));      //false
console.log(/^a?$/.test("aa"));     //false
console.log(/^a?$/.test("aab"));    //false

四、量词 {n}

表示可以出现 n
// * ==> {0,}
console.log(/^a{0,}$/.test('a'));    // true
console.log(/^a{0,}$/.test('aa'));   // true
console.log(/^a{0,}$/.test(''));     // true
console.log(/^a{0,}$/.test('abc'));  // fasle
console.log(/^a{0,}$/.test('aaab')); // fasle

五、量词 {n,}

表示可以出现 n次或者 n次以上
// + ==> {1,}
console.log(/^a{1,}$/.test('a'));    // true
console.log(/^a{1,}$/.test('aa'));   // true
console.log(/^a{1,}$/.test(''));     // fasle
console.log(/^a{1,}$/.test('abc'));  // fasle
console.log(/^a{1,}$/.test('aaab')); // fasle

六、量词 {n,m}

表示可以出现n-m次
// ? ==> {0,1}
console.log(/^a{0,1}$/.test("a"));   //true
console.log(/^a{0,1}$/.test(""));    //true
console.log(/^a{0,1}$/.test("b"));   //false
console.log(/^a{0,1}$/.test("aa"));  //false
console.log(/^a{0,1}$/.test("aab")); //false

[ 65-正则表达式-量词.html ]

14.6 括号总结

一、{} 大括号限定出现的次数

// 表示的是 n 重复两次
console.log(/chuan{2}/.test("chuanchuan"));     // false 
console.log(/chuan{2}/.test("chuann"));         // true
console.log(/chuan{2}/.test("chuann123123"));   // true

二、[] 表示一个字符出现的位置

console.log(/^[fb]oot$/.test("foot"));  // true
console.log(/^[fb]oot$/.test("boot"));  // true

三、() 用来提高优先级

console.log(/^(chuan){2}$/.test("chuanchuan")); // true

14.7 正则表达式综合案例

一、验证座机号码: [ 67-正则案例-验证座机号码.html ]

  • 直辖市座机号码:021-88888888
  • 普通地区作急哦号码:0515-12345678
  • 前区能够是三位也能够是四位,第一位必须是0,后区必须是8位;
var reg = /^0\d{2,3}-\d{8}$/;
console.log(reg.test('021-12345678'));   // true
console.log(reg.test('0515-88888888'));  // true
console.log(reg.test('0515-888880888')); // false

二、验证中文姓名 [ 68-正则案例-验证中文姓名.html ]

  • 只能是汉字
  • 长度2-6位之间
  • 汉字范围[\u4e00-\u9fa5]
  • unicode编码:万国码,其中\u4e00-\u9fa5表示的就是包含全部汉字的unicode编码
var nameReg = /^[\u4e00-\u9fa5]{2,6}$/;
console.log(nameReg.test('莫'));         // false
console.log(nameReg.test('小泽玛利亚')); // true
console.log(nameReg.test('柯南'));       // true

三、验证邮箱 [ 69-正则案例-验证邮箱.html ]

  • 前面是字母或者数字
  • 必须有@
  • @后面是字母或者数字
  • 必须有.
  • .后面是字母或者数字
var mailBoxReg = /^\w+@\w+(\.\w+)+$/;
console.log(mailBoxReg.test('18888888@qq.com')); // true

四、验证手机号码 [ 70-正则案例-验证手机号码.html ]

  • 11位数字组成
  • 号段13[0-9] 147 15[0-9] 177[0178] 18[0-9]
var mobileReg = /^(13[0-9]|147|15[0-9]|17[0178]|18[0-9])\d{8}$/;
console.log(mobileReg.test(15812345678));  // true

五、验证QQ [ 71-正则案例-验证qq.html ]

  • 只能是数字
  • 开头不能是0
  • 长度为5-11
var qqReg = /^[1-9]\d{4,10}$/;
console.log(qqReg.test(18888888)); // true

六、完整版表单验证 [ 72-正则案例-表单验证综合案例.html ]

<!-- 样式部分 -->
<style>
    body {
        background: #ccc;
    }
    .container {
        margin: 100px auto;
        width: 400px;
        padding: 50px;
        line-height: 40px;
        border: 1px solid #999;
        background: #efefef;
    }
    label {
        width: 40px;
        display: inline-block;
    }
    span {
        color: red;
    }
    span {
        margin-left: 30px;
        font-size: 12px;
    }
</style>

<!-- html 部分 -->
<div class="container">
    <label>Q Q</label><input type="text" id="inp1"><span></span><br/>
    <label>手机</label><input type="text" id="inp2"><span></span><br/>
    <label>邮箱</label><input type="text" id="inp3"><span></span><br/>
    <label>座机</label><input type="text" id="inp4"><span></span><br/>
    <label>姓名</label><input type="text" id="inp5"><span></span><br/>
</div>

<!-- js 部分 -->
<script>
    function checkReg(element, reg) {
        element.onblur = function() {
            var content = this.value;
            if (reg.test(content)) {
                this.nextElementSibling.innerHTML = "合法";
                this.nextElementSibling.style.color = "green";
            } else {
                this.nextElementSibling.innerHTML = "不合法";
                this.nextElementSibling.style.color = "red";
            }
        }
    }
    checkReg(document.getElementById("inp1"), /^[1-9]\d{4,11}$/);
    checkReg(document.getElementById("inp2"), /^(13[0-9]|14[57]|15[0-25-9]|17[0137]|18[0-9])\d{8}$/);
    checkReg(document.getElementById("inp3"), /^\w+@\w+(\.\w+)+$/);
    checkReg(document.getElementById("inp4"), /^0\d{2,3}-\d{7,8}$/);
    checkReg(document.getElementById("inp5"), /^[\u4e00-\u9fa5]{2,4}$/);
</script>

效果图:

image

14.8 正则补充知识点

这里要补充几个正则的小知识点,好比在正则里‘ |’,表示的是或。‘ g’,表示的是 global:全局,所有。‘ i’,表示的是 ignore:忽视大小写。

一、或‘|’ :

var mobileReg = /^(13[0-9]|14[57]|15[0-25-9]|17[0137]|18[0-9])\d{8}$/;

咱们能够看到在判断手机号码前三位的时候,咱们就用到了或:“|”

二、所有‘g’:

var str = '123123123123';
// 找到全部的1  替换成3
var newStr = str.replace(/1/g, 3);
console.log(newStr);  // 323323323

三、忽略大小写‘i’:

var str2 = 'abcdAAAA';
var newStr2 = str2.replace(/a/gi, 'e');
console.log(newStr2);  // ebcdeeee

上一篇:JavaScript 进阶知识 - 特效篇(一)
下一篇:JavaScript 进阶知识 - Ajax篇

相关文章
相关标签/搜索