JavaScript④(BOM、PC端网页特效)

BOM

概述

什么是BOM

BOM (BrowserObiectModel)即浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交与的对象,其核心对象是window。

BOM由一系列相关的对象构成,并且每个对象都提供了很多方法与属性。

BOM 缺乏标准,JavaScript语法的标准化组织是ECMA,DOM 的标准化组织是 W3C,BOM最初是Netscape浏览器标准的一部分。

DOM

  • 文档对象模型
  • DOM就是把[文档]当做一个[对象]来看待
  • DOM的顶级对象是document
  • DOM主要学习的是操作页面元素
  • DOM是W3C标准规范

BOM

  • 浏览器对象模型
  • 把[浏览器]当做一个[对象]来看待
  • BOM的顶级对象是window
  • BOM学习的是浏览器窗口交互的一些对象
  • BOM是浏览器厂商在各自浏览器上定义的,兼容性较差

BOM 的构成

BOM比DOM更大,它包含DOM。

 window 对象是浏览器的顶级对象,它具有双重角色。

  1. 它是JS访问浏览器窗口的一个接口。
  2. 它是一个全局对象。定义在全局作用域中的变量、函数都会变成window对象的属性和方法。

在调用的时候可以省略window,前面学习的对话框都属于window对象方法,如 alert()、prompt()等.

主意: window下的一个特殊属性 windowname

window对象的常用事件

窗口加载事件

window.onload = function() {}  //只能用一次

或者

window.addEventListener("load", function() {});  //提倡这种

window.onload是窗口(页面)加载事件,当文档内容完全加载完成会触发该事件(包括图像、脚本文件、CSS文件等),就调用的处理函数。

<body>
    <!-- 当文档内容完全加载完成会触发该事件,这样script里面的代码可以写在任何的地方都不影响使用,外链,或者head标签里都可以 -->
    <script>
        window.onload = function() {
            var btn = document.querySelector('button');
            btn.addEventListener('click', function() {
                alert('弹出窗');
            })
        }
    </script>
    <button>点击</button>
</body>

注意:
1.有了window.onload就可以把JS代码写到页面元素的上方,因为onload是等页面内容全部加载完毕再去执行处理函数。

2.window.onload传统注册事件方式只能写一次,如果有多个,会以最后windowonload 为准.

3.如果使用addEventListener 则没有限制

document.addEventListener('DOMContentLoaded',function(){})

DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片,flash等等

le9以上才支持

如果页面的图片很多的话,从用户访问到onload触发可能需要较长的时间,交互效果就不能实现,必然影响用户的体验,此时用DGMContentLoaded事件比较合适

区别:

load 等页面内容全部加载完毕,包含页面dom元素 图片 flash css 等等。
DOMContentLoaded 是DOM 加载完毕,不包含图片 falsh css 等就可以执行 加载速度比load更快一些。

调整窗口大小事件

window.onresize = function(){}
window.addEventListener("resize",function(){});

window.onresize 是调整窗口大小加载事件当触发时就调用的处理函数。

注意:
1.只要窗口大小发生像素变化,就会触发这个事件。
2我们经常利用这个事件完成响应式布局。windowinnerWidth当前屏幕的宽度。

<body>
    <div></div>
    <script>
        var div = document.querySelector('div');
        window.addEventListener('resize', function() {
            if (window.innerWidth <= 800) {
                div.style.display = 'none';
            } else {
                div.style.dispaly = 'block';
            }
        })
    </script>
</body>

定时器

两种定时器

window对象给我们提供了2个非常好用的方法-定时器。

  • setTimeout()
  • setlnterval()

setTimeout()定时器

window.setTimeout(调用函数,[延迟的毫秒数]);

①setTimeout()方法用于设置一个定时器,该定时器在定时器到期后执行调用函数。

<script>

    //   window.setTimeout(调用函数, 延时时间); 这个window在调用的时候可以省略

    //   1.这个延时时间单位是毫秒 但是可以省略, 省略默认是0

    setTimeout(function() {

        console.log('时间到了');

    }, 2000);

    //   2.这个调用函数可以直接写函数  还可以写 函数名  然后调用

    function callback () {

        console.log('时间到了');

    }

    setTimeout(callback, 3000);

    //  3.还有一种写法'函数名()'  不提倡这种写法

    function callback () {

        console.log('时间到了');

    }

    setTimeout('callback()', 3000);

    //  4.页面中可能有很多的定时器,我们经常给定时器加标识符 (每个定时器起一个名字)

    function callback () {

        console.log('时间到了');

    }

    var timer1 = setTimeout('callback()', 3000);

    var timer2 = setTimeout('callback()', 5000);

</script>

注意:

  1. window 可以省略。
  2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串'函数名()'三种形式。第三种不推荐。
  3. 延迟的毫秒数省略默认是0,如果写,必须是毫秒。
  4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。

②setTimeout()这个调用函数我们也称为回调函数 callback

普通函数是按照代码顺序直接调用。
而这个函数,需要等待时间,时间到了才去调用这个函数,因此称为回调函数。

简单理解:回调,就是回头调用的意思。上一件事干完,再头再用这个函数。

以前我们讲的 element.οnclick=function() 或者 element.addEventListener(“click”,fn); 里面的函数也是回调函数。

案例:5秒后自动关闭广告

  • 核心思路:5秒之后,就把这个广告隐藏起来
  • 用定时器setTimeout
<img src="images/bofang.png" alt="" class="">
<script>
    var ad = document.querySelector('.ad');
    setTimeout(function() {
        ad.style.display = 'none';
    }, 5000);
</script>

停止 setTimeout()定时器

window.clearTimeout (timeoutID)

window.clearTimeout ()方法取消了先前通过调用 setTimeout)建立的定时器

注意:

1.window 可以省略

2.里面的参数就是定时器的标识符

 setInterval()定时器

window.setInterval(回调函数, [间隔毫秒数]);

setInterval()方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。

  1. window 可以省略。
  2. 这个调用函数可以直接写函数,或者写函数名或者采取字符串'函数名()'三种形式。第三种不推荐。
  3. 间隔的毫秒数省略默认是0,如果写,必须是毫秒,表示每隔多少毫秒就自动调用这个函数
  4. 因为定时器可能有很多,所以我们经常给定时器赋值一个标识符。

 案例:京东倒计时

 分析:

  • 这个倒计时是不断变化的,因此需要定时器来自动变化(setlnterval)
  • 三个黑色盒子里面分别存放时分秒
  • 三个黑色盒子利用innerHTML放入计算的小时分钟秒数
  • 第一次执行也是间隔毫秒数,因此刚刷新页面会有空白
  • 最好采取封装函数的方式,这样可以先调用一次这个函数,防止刚开始刷新页面有空白问题
<head>
        span {
            display: inline-block;
            width: 60px;
            height: 80px;
            background-color: #ef1f1f;
            text-align: center;
            line-height: 80px;
            color: #fff;
            font-size: 30px;
        }
    </style>
</head>
<body>
    <div>
        <h1>京东秒杀</h1>
        <span class="hour">1</span>
        <span class="minute">2</span>
        <span class="sexond">3</span>
    </div>
    <script>
        //  1.获取元素
        var hour = document.querySelector('.hour');  //  小时的红色盒子
        var minute = document.querySelector('.minute');  //  分钟的红色盒子
        var sexond = document.querySelector('.sexond');  //  秒数的红色盒子
        var inputTime= +new Date('2030-5-1 18:00:00');  //  返回的是用户输入时间总的毫秒数
        countDown();  //  我们先调用一次  防止第一次执行也是间隔毫秒数,因此刚刷新页面会有空白
        function countDown(time) {
            var nowTime = +new Date();  //  返回的是当前时间总的毫秒数
            var times = (inputTime - nowTime) / 1000;  //  剩余时间总的秒数
            var h = parseInt(times / 60 / 60 % 24);  //计算小时
            h = h < 10 ? '0' + h : h ;
            hour.innerHTML = h;  //  把剩余的小时给  小时的红色盒子
            var m = parseInt(times / 60 % 60);  // 计算分数
            m = m < 10 ? '0' + m : m ;
            minute.innerHTML = m;  //  把剩余的分钟给  分钟的红色盒子
            var s = parseInt(times % 60);   //计算当前秒数
            s = s < 10 ? '0' + s : s ;
            sexond.innerHTML = s;  //  把剩余的秒数给  秒数的红色盒子
        }
        //  2.开启定时器  每隔一秒调用一次
        setInterval(countDown, 1000);  // 这里为0秒,也可以防止刚刷新页面会有空白
    </script>
</body>

停止 setInterval()定时器

window.clearInterval(intervalID);

clearInterval()方法取消了先前通过调用 setInterval()建立的定时器。

注意:

1.window 可以省略。

2.里面的参数就是定时器的标识符。

<button class="begin">开启定时器</button>
<button class="stop">停止定时器</button>
<script>
    //  点击按钮开启定时器  点击停止定时器
    var begin = document.querySelector('.begin');  
    var stop = document.querySelector('.stop');  
    var timer = null;  //  全局变量  不能写里面
    begin.addEventListener('click', function() {
        timer = setInterval(function() {
            console.log('你好吗~');
        }, 1000);
    })
    stop.addEventListener('click', function() {
        clearInterval(timer);
    })
</script>

案例:发送短信

点击按钮后,该按钮60秒之内不能再次点击,防止重复发送短信

 分析:

  • 按钮点击之后,会禁用 disabled为true。
  • 同时按钮里面的内容会变化,注意button里面的内容通过innerHTML修改。
  • 里面秒数是有变化的,因此需要用到定时器。
  • 定义一个变量,在定时器里面,不断递减。
  • 如果变量为0说明到了时间,我们需要停止定时器,并且复原按钮初始状态。
<body>
    手机号:<input type="number"><button>发送</button>
    <script>
        // 按钮点击之后,会禁用 disabled为true。
        // 同时按钮里面的内容会变化,注意button里面的内容通过innerHTML修改。
        // 里面秒数是有变化的,因此需要用到定时器。
        // 定义一个变量,在定时器里面,不断递减。
        // 如果变量为0说明到了时间,我们需要停止定时器,并且复原按钮初始状态。
        var btn = document.querySelector('button');
        var time = 10;  // 定义剩下的秒数
        btn.addEventListener('click', function() {
            btn.disabled = true;
            var timer = setInterval(function() {
                if(time == 0) {
                    // 清除定时器和复原按钮
                    clearInterval(timer);
                    btn.disabled = false;
                    btn.innerHTML = '发送';
                    time = 10;  //  这个10需要重新开始
                } else {
                    btn.innerHTML = '还剩下' + time +'秒';
                    time--;
                }
            }, 1000);
        })
    </script>
</body>

this

this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,一般情况下this的最终指向的是那个调用它的对象。

<body>

    <button>点击</button>

    <script>

        // this 指向问题 一般情况下this的最终指向的是那个调用它的对象

        // 全局作用域或者普通函数中this指向全局对象window( 注意定时器里面的this指向window)。

        console.log(this);  // 结果是Window

        function fn() {

            console.log(this);  // 结果是Window

        }

        setTimeout(function() {

            console.log(this);  // 结果是Window

        }, 1000);

        // 方法调用中谁调用this指向谁  方法是谁调用指向谁

        var o = {

            sayHi: function() {

                console.log(this);  // this 指向的是这个对象o

            }

        }

        o.sayHi;

        var btn = document.querySelector('button');

        btn.onclick = function() {

            console.log(this);  // this指向的是btn这个按钮

        }

        btn.addEventListener('click', function() {

            console.log(this);  // this指向的是btn这个按钮

        })

        // 构造函数中this指向构造函数的实例

        function Fun() {

            console.log(this); // this指向的是fun  实例对象

        }

        var fun = new Fun();

    </script>

</body>

Js执行队列

JS是单线程

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这是因为Javascript这门脚本语言诞生的使命所致-Javasipt 是为处理页面中用户的交互,以及操作DOM而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

 同步和异步

为了解决这个问题,利用多核CPU的计算能力,HTML5提出Web Worker 标准,允许JavaScript脚本创建多个线程。于是,JS中出现了同步异步

同步

前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。

异步

你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。

他们的本质区别:这条流水线上各个流程的执行顺序不同。

 同步任务

 同步任务都在主线程上执行,形成一个执行栈

异步任务

JS的异步是通过回调函数实现的。

一般而言,异步任务有以下三种类型:

1、普通事件,如click、resize等

2、资源加载,如load、error等

3、定时器,包括setlnterval、setTimeout等

异步任务相关回调函数添加到任务队列中(任务队列也称为消息队列)

 

 JS执行机制
  1. 先执行执行栈中的同步任务。
  2. 异步任务(回调函数)放入任务队列中。
  3. 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。

 示例:

 知识概括:

 由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环( eventloop )

location 对象

什么是location 对象?

window 对象给我们提供了一个location 属性用于获取或设置窗体的URL,并且可以用于解析URL。因为这个属性返回的是一个对象,所以我们将这个属性也称为location 对象

URL

统一资源定位符(Uniform Resource Locator, URL)是联网上标准资源的地址,与联网上的每个文件都有个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

URL的一般语法格式为:

protocol://host[:port]/path/[?query]#fragment

示例:http://www.itcast.cn/index.html?name=andy&age=18#link

 location 对象的属性

location对象属性返回值

location.href    ⭐⭐

获取或者设置 整个URL
location. host返回主机 (域名)  www.itheima.com
location.port返回端口号 如果未写返回 空字符串
location.pathname返回路径
location. search    ⭐⭐返回参数
location. hash返回片段 #后面内容 常见于链接 锚点

<body>
    <button>点击</button>
    <script>
        var btn = document.querySelector('button');
        btn.addEventListener('click',function() {
            //  console.log(location.href);  //  点击按钮得到当前页面的URL
            location.href = 'http://baidu.com';  //  点击按钮跳转到新的页面地址
        })
    </script>
</body>

案例:5秒钟之后自动跳转页面

案例分析:

  • 利用定时器做倒计时效果
  • 时间到了,就跳转页面。使用location.href
<body>
    <div></div>
    <script>
        var div = document.querySelector('div');
        var timer = 5;
        setInterval(function() {
            if(timer == 0) {
                location.href = 'http://baidu.com';  //  如果timer倒计时 到0,回到页面
            } else {
                div.innerHTML = '您将在'+ timer +'秒钟之后跳转到首页';
                timer--;
            }            
        }, 1000)
    </script>
</body>

案例 : 获取 URL 参数数据

主要练习数据在不同页面中的传递

案例分析:

  1. 第一个登录页面,里面有提交表单,action提交到index.html页面。
  2. 第二个页面,可以使用第一个页面的参数,这样实现了一个数据不同页面之间的传递效果。
  3. 第二个页面之所以可以使用第一个页面的数据,是利用了URL里面的location.search参数。
  4. 在第二个页面中,需要把这个参数提取。
  5. 第一步  去掉?利用substr
  6. 第二步  利用=号分割 键 和 值  split('=')

login.html文件里面的:

<body>

    <form action="index2.html">

        用户名:<input type="text" name="uname">

        <input type="submit" value="登录">

    </form>

</body>

index2.html文件里面的:

<body>

    <div></div>

    <script>

        console.log(location.search);  //  ?uname=andy

        //  第一步  去掉?利用 substr('起始位置',截取几个字符)

        var params = location.search.substr(1);  //  不写截取几个字符就表示从第一个字符截取到最后

        //  var params = location.search.substring(1);  //  substring也可以

        console.log(params);  //  结果为uname=andy

        //  第二步  利用=号把字符串分割成数组 split('=');

        var arr = params.split('=');

        console.log(arr);  //  结果为['uname', 'andy']

        var div = document.querySelector('div');

        div.innerHTML = arr[1] + '欢迎您!';

    </script>

</body>

location 对象的方法

location对象方法返回值
location.assign()跟 href 一样,可以跳转页面 (也称为重定向页面)
location.replace()替换当前页面,因为不记录历史,所以不能后退页面
location.reload()重新加载页面,相当于刷新按钮或者 f5 如果参数为true 强制刷新 ctrl+f5

<body>
    <button>点击</button>
    <script>
        var btn = document.querySelector('button');
        btn.addEventListener('click', function() {
            //  location.assign记录浏览历史,所以可以实现后退功能
            location.assign('http://www.baidu.com');
            //  location.replace不记录浏览历史,所以不可以实现后退功能
            location.replace('http://www.baidu.com');
            //  location.reload重新加载页面,相当于刷新按钮
            location.reload(true);  // 加true为强制刷新
        })
    </script>
</body>

navigator 对象

navigator对象包含有关浏览器的信息,它有很多属性,我们最常用的是userAgent,该属性可以返回由客户机发送服务器的user-agent头部的值。
下面前端代码可以判断用户那个终端打开页面,实现跳转。

if((navigator.userAgent.match(/(phone | pad | pod | iphone | iPod | ios | iPad | Android |

Mobile | BlackBerry | IEMobile | MQQBrowser | JUC | Fennec | wOSBrowser | BrowserNG | Webos | Symbian | windows Phone)/i))) {

        window.location.href = "";    // 手机

} else {

        window.location.href = "";    // 电脑

}

示例:

//  在pc端写
<script>
        if ((navigator.userAgent.match(/(phone | pad | pod | iphone | iPod | ios | iPad | Android |Mobile | BlackBerry | IEMobile | MQQBrowser | JUC | Fennec | wOSBrowser | BrowserNG | Webos | Symbian | windows | Phone) / i))) {
            window.location.href = "../H5/index.html";   // 手机
        }
</script>

-------------------------

history 对象

window对象给我们提供了一个history 对象,与浏览器历史记录进行交互该对象包含用户(在浏览器窗口中)访问过的 URL。

history对象方法作用
back()可以后退功能
forward()前进功能
go(参数)前进后退功能 参数如果是 1 前进1个页面如果是-1 后退1个页面

// 首页写
<body>
    <a href="list.html">点击我去往列表页list.html</a>
    <button>前进</button>
    <script>
        var btn = document.querySelector('button');
        btn.addEventListener('click', function() {
            history.forward();
            //  history.go(1);  //  也可以写go  go(1)表示前进一步  go(-1)表示后退一步
        })
    </script>
</body>

-----------------------

//  列表页写
<body>
    <a href="index.html">点击我去往首页index.html</a>
    <button>前进</button>
    <script>
        var btn = document.querySelector('button');
        btn.addEventListener('click', function() {
            history.back();
            //  history.go(1);  //  也可以写go  go(1)表示前进一步  go(-1)表示后退一步
        })
    </script>
</body>

history对象一般在实际开发中比较少用,但是会在一些OA办公系统中见到。


PC端网页特效

元素偏移量 offset 系列

概述

offset 翻译过来就是偏移量,我们使用offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。

  • 获得元素距离带有定位父元素的位置
  • 获得元素自身的大小(宽度高度)
  • 注意:返回的数值都不带单位

offset 系列常用属性:

offset系列属性作用
element.offsetParent返回作为该元素带有定位的父级元素 如果父级都没有定位则返回body
element.offsetTop返回元素相对带有定位父元素上方的偏移
element.offsetLeft返回元素相对带有定位父元素左边框的偏移
element.offsetWidth返回自身包括padding、 边框、内容区的宽度,返回数值不带单位
element.offsetHeight返回自身包括padding、边框、内容区的高度,返回数值不带单位

<head>
        .father {
            position: relative;
            width: 200px;
            height: 200px;
            background-color: blue;
            margin: 100px;
        }
        .son {
            width: 100px;
            height: 100px;
            background-color: purple;
            margin-left: 45px;
        }
        .w {
            width: 200px;
            height: 200px;
            background-color: red;
            padding: 10px;
        }
    </style>
</head>

<body>
    <div class="father">
        <div class="son"></div>
    </div>
    <div class="w"></div>
    <script>
        var father = document.querySelector('.father');
        var son = document.querySelector('.son');
        // 1.可以得到元素的偏移 位置 返回的不带单位的数值
        console.log(father.offsetTop);  // 100  动态的变化 
        console.log(father.offsetLeft);  // 100
        console.log(son.offsetLeft);   //  45  因为父盒子father有定位 否则以body为准
        var w = document.querySelector('.w');
        // 2.可以得到元素的大小 宽度和高度 是包含padding + border + width
        console.log(w.offsetWidth);  // 220  padding值和boder值都会撑大盒子
        console.log(w.offsetHeight);  // 220  padding值boder值都会撑大盒子
        // 3.返回带有定位的父亲 否则返回的是body
        console.log(son.offsetParent);   //  结果为 <div class="father">...</div>  如果没有定位 则返回body
        console.log(son.parentNode);   //  这个也是返回父亲  返回的是最近一级的父亲  不管父亲有没有定位
    </script>
</body>

offset 与 style 区别

offset
  • offset可以得到任意样式表中的样式值
  • offset 系列获得的数值是没有单位的
  • offsetWidth 包含padding+border+width
  • offsetWidth等属性是只读属性,只能获取不能赋值
  • 所以,我们想要获取元素大小位置,用offset更合适
style
  • style只能得到行内样式表中的样式值
  • style.width获得的是带有单位的字符串
  • style.width获得不包含padding和border的值
  • style.width是可读写属性,可以获取也可以赋值
  • 所以,我们想要给元素更改值,则需要用style改变

案例:获取鼠标在盒子内的坐标

 分析:

  • 我们在盒子内点击, 想要得到鼠标距离盒子左右的距离。
  • 首先得到鼠标在页面中的坐标 ( e.pageX,e.pageY)。
  • 其次得到盒子在页面中的距离(box.offsetLeft,box.offsetTop)。
  • 用鼠标距离页面的坐标减去盒子在页面中的距离,得到 鼠标在盒子内的坐标。
  • 如果想要移动一下鼠标,就要获取最新的坐标,使用鼠标移动事件mousemove
  • <head>
        <style>
            .box {
                width: 200px;
                height: 200px;
                background-color: blue;
                color: #fff;
            }
        </style>
    </head>
    
    <body>
        <div class="box"></div>
        <script>
            var box = document.querySelector('.box');
            box.addEventListener('mousemove', function(e) {
                // console.log(e.pageX);  //  获得鼠标在页面中的X坐标  也就是鼠标在页面中距离左侧的距离
                // console.log(e.pageY);  //  获得鼠标在页面中的Y坐标  也就是鼠标在页面中距离上方的距离
                // console.log(box.offsetLeft);  //  获得盒子在页面中左侧的距离
                var x = e.pageX - this.offsetLeft;  //  获得鼠标在盒子中的X坐标  也就是鼠标在盒子中距离左侧的距离
                var y = e.pageY - this.offsetTop;  //  获得鼠标在盒子中的Y坐标  也就是鼠标在盒子中距离上方的距离
                this.innerHTML = 'x坐标是' + x + 'x坐标是' + y;
            })
        </script>
    </body>

案例:模态框(弹窗)拖拽

弹出框,我们也称为模态框

  • 点击弹出层,会弹出模态框,并且显示灰色半透明的遮挡层。
  • 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
  • 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
  • 鼠标松开,可以停止拖动模态框移动。

 案例分析:

  1. 点击弹出层,模态框和遮挡层就会显示出来display:block;
  2. 点击关闭按钮,模态框和遮挡层就会隐藏起来display:none;
  3. 在页面中拖拽的原理:鼠标按下并且移动,之后松开鼠标。
  4. 触发事件是鼠标按下mousedown, 鼠标移动mousemove 鼠标松开mouseup
  5. 拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的let和top值,这样模态框可以跟着鼠标走了。
  6. 鼠标按下触发的事件源是最上面一行,就是id 为 title。
  7. 鼠标的坐标减去鼠标在盒子内的坐标,才是模态框真正的位置。
  8. 鼠标按下,我们要得到鼠标在盒子的坐标。
  9. 鼠标移动,就让模态框的坐标 设置为 : 鼠标坐标减去盒子坐标即可,注意移动事件写到按下事件里面。
  10. 鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除。
<head>
        * {
            margin: 0;
            padding: 0;
            cursor: default;
        }
        a {
            text-decoration: none;
        }
        .login-header {
            display: inline-block;
            width: 200px;
            height: 60px;
            border: 1px solid blue;
            border-radius: 5px;
            margin: 50px 200px;
        }
        #link {
            margin-left: 40px;
            line-height: 60px;
        }
        .login {
            display: none;
            width: 400px;
            height: 300px;
            background-color: #fff;
            border-radius: 5px;
            border: 1px solid blue;
            box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.2);
            margin: 0 100px;
            position: absolute;
            left: auto;
            top: auto;
        }
        #denglu {
            position: relative;
            font-size: 24px;
            left: 138px;
            top: 15px;
        }
        .login-title {
            position: relative;
            width: 400px;
            height: 50px;
        }
        .close-login {
            position: absolute;
            top: -15px;
            right: -15px;
            display: block;
            width: 40px;
            height: 40px;
            background-color: #dc3232;
            color: #fff;
            text-align: center;
            line-height: 40px;
            border-radius: 20px;
            font-size: 13px;
        }
        .login-input-content,
        .login-input {
            margin: 40px 14px;
        }
        .username,
        .password {
            width: 250px;
            height: 30px;
        }
        #yonghuming {
            margin-right: 28px;
        }
        .login-button {
            display: inline-block;
            width: 220px;
            height: 40px;
            background-color: rgb(22, 103, 234);
            border-radius: 5px;
            margin-left: 86px;
        }
        #login-button-submit {
            color: #fff;
            margin-left: 88px;
            line-height: 40px;
        }
        .login-bg {
            display: none;
            width: 100%;
            height: 100%;
            position: fixed;
            top: 0;
            left: 0;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: -1;
        }
    </style>
</head>

<body>
    <div class="login-header"><a id="link" href="javascript:;">点击弹出登录框</a></div>
    <div id="login" class="login" >
        <div id="title" class="login-title">
            <h2 id="denglu">登录会员</h2>
            <span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
        </div>
        <div class="login-input-content">
            <div class="login-input">
                <label id="yonghuming">用户名:</label>
                <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="username">
            </div>
            <div class="login-input">
                <label>登陆密码:</label>
                <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="password" >
            </div>
        </div>
        <div id="loginBtn" class="login-button"><a href="javascript:void(0); " id="login-button-submit">登录</a></div>
    </div>
    <!-- 遮盖层 -->
    <div id="bg" class="login-bg"></div>
    <script>
        //  1.获取元素
        var login = document.querySelector('.login');
        var mask = document.querySelector('.login-bg');
        var link = document.querySelector('#link');
        var closeBtn = document.querySelector('#closeBtn');
        var title = document.querySelector('#title');
        //  2.点击弹出层这个链接link  让mask和login显示出来
        link.addEventListener('click', function() {
            mask.style.display = 'block';
            login.style.display = 'block';
        })
        //  3.点击 closeBtn 就隐藏 mask 和login
        closeBtn.addEventListener('click', function() {
            mask.style.display = 'none';
            login.style.display = 'none';
        })
        //  4.开始拖拽
        //  (1)鼠标按下,获得鼠标在盒子内的坐标
        title.addEventListener('mousedown', function(e) {
            var x = e.pageX - login.offsetLeft;
            var y = e.pageY - login.offsetTop;
            //  (2)鼠标移动的时候,把鼠标在页面中的坐标,减去鼠标在盒子内的坐标就是模态框的left和top值
            // document.addEventListener('mousemove', function(e) {
            //     login.style.left = e.pageX - x + 'px';
            //     login.style.top = e.pageY - y + 'px';
            // })
            //  上面的function可以这样写,这样上下都可以调用了
            document.addEventListener('mousemove', move)
            function move(e) {
                login.style.left = e.pageX - x + 'px';
                login.style.top = e.pageY - y + 'px';
            }
            //  (3)鼠标弹起,就让鼠标移动事件解除
            document.addEventListener('mouseup', function() {
                document.removeEventListener('mousemove', move);
            })
        })
    </script>
</body>

案例:仿京东放大镜

 1.在商品图上加一个半透明的盒子,采取定位的方式,黄色半透明的盒子,先隐藏起来dispaly:none;

 2.准备一个大盒子500*500,在商品图的右侧,鼠标在商品图上,大盒子显示,鼠标离开大盒子隐藏。大盒子先隐藏起来dispaly:none;

 

 3.放大镜效果分析:

①整个案例可以分为三个功能模块
②鼠标经过小图片盒子,黄色的遮挡层和大图片盒子显示,离开隐藏2个盒子功能

③黄色的遮挡层跟随鼠标功能。

  • 黄色的遮挡层跟随鼠标功能
  • 把鼠标坐标给遮挡层不合适,因为遮挡层坐标以父盒子为准
  • 首先是获得鼠标在盒子的坐标
  • 之后把数值给遮挡层做为left 和top值
  • 此时用到鼠标移动事件,但是还是在小图片盒子内移动
  • 遮挡层不能超出小图片盒子范围
  • 如果小于零,就把坐标设置为0
  • 等于说黄色盒子只能移动产品图400减黄盒子300等于100像素的距离
  • 如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离
  • 遮挡层的最大移动距离:小图片盒子宽度减去遮挡层盒子宽度

④移动黄色遮挡层,大图片跟随移动功能。

  • 最终公式

 

 大盒子也需要有定位和 top left值 

以下是js代码:

// window.addEventListener 第一句的意思是这里面的js代码是HTML和CSS文件都加载完了,再加载此js
window.addEventListener('load', function() {
    var preview_img = document.querySelector('.preview_img');
    var mask = document.querySelector('.mask');
    var big = document.querySelector('.big');
    //  1.当我们鼠标经过/离开 preciew_img 就显示/隐藏  mask遮挡层和big大盒子
    //  鼠标显示
    preview_img.addEventListener('mouseover', function() {
        mask.computedStyleMap.dispaly = 'block';
        big.computedStyleMap.dispaly = 'block';
    })
    //  鼠标隐藏
    preview_img.addEventListener('mouseout', function() {
        mask.computedStyleMap.dispaly = 'none';
        big.computedStyleMap.dispaly = 'none';
    })
    //  2.鼠标移动 让黄色的盒子跟着鼠标来走
    preview_img.addEventListener('mousemove', function(e) {
        //  <1>先算出鼠标在盒子内的坐标
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        //  <2>把鼠标给黄色的盒子  鼠标要在黄色盒子的中间 所以黄色盒子需要往左走150px,向上走150px 各减150
        //  150是盒子的高度300 的一半 也就是mask.offsetWidth / 2 这样就可以随着盒子的变化而自动变化了
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y -mask.offsetHeight / 2;
        //  <3>加判断 如果x坐标小于0 就让他停在0的位置 思路上应该是先考虑第<4>步
        if (maskX <= 0) {
            maskX = 0;  // 限制黄色盒子左边
        } else if (maskX >= preview_img.offsetWidth - mask.offsetWidth) {  //  preview_img.offsetWidth - mask.offsetWidth 产品图的宽度减黄盒子的宽度就是黄盒子能向右移动的最远距离,再往右黄盒子就出产品盒子了
            maskX = preview_img.offsetWidth - mask.offsetWidth;  // 限制黄色盒子右边
        }
        if (maskY <= 0) {
            maskY = 0;  // 限制黄色盒子上边
        } else if (maskY >= preview_img.offsetHeight - mask.offsetHeight) {  //  产品图的高度减黄盒子的高度就是黄盒子能向下移动的最远距离,再往下黄盒子就出产品盒子了
            maskX = preview_img.offsetHeight - mask.offsetHeight;  // 限制黄色盒子下边
        }
        // <4> 黄色盒子的左右坐标跟着鼠标改变
        mask.style.left = maskX +'px';
        mask.style.top = maskY +'px';
        // 大图片的移动距离 = 遮挡层移动距离 * 大图片移动距离 / 遮挡层的最大移动距离
        var maskMax = preview_img.offsetWidth - mask.offsetWidth;  //  遮挡层的最大移动距离 注意:因为这里都是正方形盒子,所以不需要算高
        var bigImg = document.querySelector('.bigImg');  // 获取大图片
        var bigMax = bigImg.offsetWidth - big.offsetWidth;  // 大图片移动距离  等于  大图片减大盒子距离
        // 遮挡层移动距离就是 maskX maskY
        // 大图片的移动距离 bigX bigY
        var bigX = maskX * bigMax / maskMax;
        var bigY = maskY * bigMax / maskMax;
        //  黄色盒子往右走,大盒子显示的内容往左走,两个是相反方向所以big前面要加负号-
        bigImg.style.left = -bigX +'px';
        bigImg.style.top = -bigY +'px';
    })
})

元素可视区client系列

概念

client 翻译过来就是客户端,我们使用client 系列的相关属性来获取元素可视区的相关信息。通过 client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

client系列属性作用
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.clientWidth返回自身包括padding、 内容区的宽度,不含边框,返回数值不带单位
element.clientHeight返回自身包括padding、 内容区的高度,不含边框,返回数值不带单位

注意:

  • cilent 和我们offsetwidth 最大的区别就是 不包含边框border 包含内边距padding

淘宝 flexible.js 源码分析

立即执行函数  (function() {})();

1.立即执行函数概念:不需要调用,立马能够自己执行的函数

2.两种写法:

(function() {})();

(function(){}());

3.主要作用:创建一个独立的作用域

<script>
    // 1.立即执行函数:不需要调用,立马能够自己执行的函数
    function fu() {
        console.log(1);
    }
    fu();  //  以前的写法,调用才能执行
    //  2.两种写法:(function() {})()  或者  (function(){}());
    (function(a) {
       console.log(a);  // 不需要调用直接执行
    })(2);  // 第二个小括号可以看做是调用函数 可以看做是实参
    (function(a,b) {
       console.log(a + b);  // 不需要调用直接执行
    })(2,3);
    //  3.立即执行函数最大的作用就是 独立创建了一个作用域 里面所有的变量都是局部变量 不会有命名冲突的情况
    (function(a) {
       console.log(a);
       var num = 10;
    })(2);
    (function(a,b) {
       console.log(a + b);
       var num = 10;
    })(2,3);
</script>

下面三种情况都会刷新页面都会触发load 事件
1.a标签的超链接
2.F5或者刷新按钮(强制刷新)
3.前进后退按钮

但是火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。
所以此时后退按钮不能刷新页面
此时可以使用 pageshow事件来触发。,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加

(function flexuble(window, document) {
    //  获取的html的根元素
    var docEl = document.documentElement
    // dpr 物理像素比
    var dpr = window.devicePixelRatio || 1
    // adjust body fint size 设置我们body 的字体大小
    function setBodyFontSie() {
        //  先判断有没有body这个元素
        //  如果页面中有body 这个元素,就设置body的字体大小  nody这个元素就是12乘这个物理像素比  PC端就乘1,就是12像素,如果是移动端物理像素比是2,就是24像素
        if (document.body) {
            document.body.style.fontSize = (12 * dpr) + 'px'
        } else {
            // 如果页面中没有body这个元素,则等着我们页面只要的DOM元素加载完毕再去设置body字体大小  防止js文件没有在body里面引用 而是在上面的head里面引用
            document.addEventListener('DOMContentLoaded', setBodyFontSie)
        }
    }
    setBodyFontSie();
    // set 1rem = viewWidth / 10  设置html元素的字体大小
    function setRemWidth() {
        var rem = docEl.clientWidth / 10  // 把整个屏幕划分了10等份
        docEl.style.fontSize = rem + 'px'
    }
    setRemWidth();
    // reset rem unit on page resize  当我们页面尺寸大小发生变化的时候 要重新设置下rem 的大小
    // 给window添加一个事件resize  当尺寸发生变化的时候调用setRemWidth这个函数
    window.addEventListener('resize', setRemWidth)
    //  pageshow 是我们重新加载页面触发的事件 也就是说 页面一重新加载就会执行这个pageshow事件
    window.addEventListener('pageshow', function(e) {
        // e.persisted 返回的是true 就是说如果这个亚眠是从缓存取过来的页面,也需要从新加载,计算以下rem的大小
        if(e.persisted) {
            setRemUnit()
        }
    })
    // detect 1.5px support 有些移动端的浏览器不支持0.5像素的写法 下面是解决方案
    if(dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.boeder = '.5px solid transparent'
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
            docEl.classList.add('hairlines')
        }
        docEl.removeChild(fakeBody)
    }
}(window, document))

元素滚动 scroll 系列

元素scroll 系列属性

scroll翻译过来就是滚动的,我们使用 scroll系列的相关属性可以动态的得到该元素的大小滚动距离

scroll系列属性作用
element.scrollTop返回被卷去的上侧距离,返回数值不带单位
element.scrollLeft返回被卷去的左侧距离,返回数值不带单位
element.scrollWidth返回自身实际的宽度,不含边框,包含padding,返回数值不带单位
element.scrollHeight返回自身实际的高度,不含边框,包含padding,返回数值不带单位

解析图

 页面被卷去的头部

如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发onscroll事件。

<head>
        div {
            width: 200px;
            height: 200px;
            background-color: blue;
            border: 20px solid #ccc;
            padding: 10px;
            /* 自动出现滚动条 */
            overflow: auto;  
        }
    </style>
</head>

<body>
    <div>我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容</div>
<script>
    // scroll系列
    var div = document.querySelector('div');
    console.log(div.scrollHeight);  // 258 如果盒子里面的文字超出盒子,那么scroll也会一起变化
    console.log(div.clientHeight);  // 220  而client不会变化 但是会包含padding值
    // onscroll滚动事件 当我们滚动条发生变化会触发的事件
    div.addEventListener('scroll', function() {
        console.log(div.scrollTop);
    })
</script>

案例:仿淘宝固定右侧侧边栏

1.原先侧边栏是绝对定位
2.当页面滚动到一定位置,侧边栏改为固定定位
3.页面继续滚动,会让返回顶部显示出来

分析:

  • 需要用到页面滚动事件scroll 因为是页面滚动,所以事件源是document
  • 滚动到某个位置,就是判断页面被卷去的上部值
  • 页面被卷去的头部:可以通过window.pageyoffset 获得 如果是被卷去的左侧 window.pageXOffset
  • 注意,元素被卷去的头部是element.scrollTop,如果是页面被卷去的头部则是window.pageYOffset
<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            cursor: default;
        }
        .w {
            width: 1200px;
            height: 800px;
            margin: 20px auto;
            font-size: 50px;
            line-height: 500px;
            text-align: center;
            
        }
        .header {
            background-color: blue;
        }
        .banner {
            background-color: yellow;
        }
        .mian {
            background-color: yellowgreen;
        }
        body {
            position: relative;
        }
        .slider-bar {
            width: 50px;
            height: 100px;
            right: 598px;
            position: absolute;
            background-color: rgb(0, 217, 255);
            top: 368px;
        }
        span {
            display: none;
            position: absolute;
            bottom: 0;
        }
    </style>
</head>

<body>
    <div class="slider-bar">
        <span class="goBack">返回顶部</span>
    </div>
    <div class="header w">头部区域</div>
    <div class="banner w">banner区域</div>
    <div class="mian w">主体部分</div>
<script>
    var sliderbar = document.querySelector('.slider-bar');
    var banner = document.querySelector('.banenr');
    // banner.offestTop就是被卷去头部的大小 一定要写外面
    var bannerTop = banner.offsetTop;
    // 当我们侧边栏固定定位之后应该变化的数值
    var sliderbarTop = sliderbar.offsetTop - bannerTop;
    // 获取mian主体事件
    var mian = document.querySelector('.mian')
    var goBack = document.querySelector('.goBack')

    // 页面滚动事件
    document.addEventListener('scroll', function() {
        // window.pageYOffset页面被卷去头部
        // console.log(window.pageYOffset);
        // 当我们页面被卷去的头部大于等于了860 此时 侧边栏就要改为固定定位
        if(window.pageYOffset >= 860) {
            sliderbar.style.position = 'fixed';
            sliderbar.style.top = sliderbarTop + 'px';
        } else {
            sliderbar.style.position = 'absolute';
            sliderbar.style.top = '860px';
        }
        // 当我们页面滚动到main盒子,就显示goback模块
        if(window.pageYOffset >= mianTop) {
            goBack.style.dispaly = 'block';
        } else {
            goBack.style.dispaly = 'none';
        }
    })
</script>
</body>

页面被卷去的头部兼容性解决方案

需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:
1.声明了DTD,使用document.documentElement.scrollTop
2未声明DTD,使用 document.body.scrollTop
3.新方法window.pageYoffset 和window.pagexoffset,IE9开始支持

 function getScroll() {

    return {

        left: window.pageXOffset || document.documentElement.scrollLeft ||document.body.scrollLeft || 0,

        top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0

    };

}

gotScroll().left

//  使用的时候 调用这个函数 使用这个函数中的left值

三大系列总结(offst、client、scroll)

三大系列大小对比作用
element.offsetWidth返回自身包括padding、 边框(下两个不包含)、内容区的宽度,返回数值不带单位
element.clientWidth返回自身包括padding 、 内容区的宽度,不含边框,返回数值不带单位(不包含文字超出的地方)
element.scrollWidth返回自身实际的宽度,不含边框,返回数值不带单位(包含文字超出的地方)

 

 他们主要用法:

  1.  offset系列经常用于获得元素位置 offsetLeft offsetTop
  2. client经常用于获取元素大小 clientWidth clientHeight
  3. scroll经常用于获取滚动距离 scrollTop scrollLeft
  4. 注意页面滚动的距离通过window.pageXOffset 获得

 mouseenter 和mouseover的区别

 mouseenter鼠标事件

  • 当鼠标移动到元素上时就会触发mouseenter事件
  • 类似mouseover,它们两者之间的差别是
  • mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发mouseenter 只会经过自身盒子触发

原因:(父盒子上有鼠标触发事件的时候)

1.mouseover 鼠标经过父盒子触发,在移动到里面的子盒子也会触发,原因是里面的子盒子会向外层一层一层的冒泡,所以当冒到外面的父盒子的时候就会又触发一次

2.mouseenter 则不会冒泡,经过父盒子会输出,经过子盒子则不会输出

动画函数封装

动画实现原理

核心原理:通过定时器setlnterval()不断移动盒子位置

实现步骤:
1.获得盒子当前位置
2.让盒子在当前位置加上1个移动距离
3.利用定时器不断重复这个操作
4.加一个结束定时器的条件
5.注意此元素需要添加定位,才能使用element.style.left

<head>
        div {
            position: absolute;
            left: 0;
            width: 100px;
            height: 100px;
            background-color: blue;
        }
    </style>
</head>

<body>
<div></div>
<script>
    var div = document.querySelector('div');
    // 每30毫秒就向前1像素
    var timer = setInterval(function() {
        if(div.offsetLeft >= 400) {
            //  停止动画 本质是停止定时器
            clearInterval(timer);
        }
        div.style.left = div.offsetLeft + 1 + 'px';
    }, 30);
</script>

动画函数简单封装

注意函数需要传递2个参数,动画对象移动到的距离。

<body>
<div></div>
<span></span>
<script>
    function animate(obj, target) {
        var timer = setInterval(function() {
        if(obj.offsetLeft >= target) {
            clearInterval(timer);
        }
        obj.style.left = obj.offsetLeft + 1 + 'px';
    }, 30);
    }
    var div = document.querySelector('div');
    var span = document.querySelector('span');
    // 调用函数
    animate(div, 300);
    animate(span, 400);
</script>
</body>

动画函数给不同元素记录不同定时器

如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。
核心原理:利用JS是一门动态语言,可以很方便的给当前对象添加属性。

<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            cursor: default;
        }
        a {
            text-decoration: none;
        }
        div {
            position: absolute;
            left: 0;
            top: 100px;
            width: 100px;
            height: 100px;
            background-color: blue;
        }
        span {
            position: absolute;
            left: 0;
            top: 200px;
            width: 50px;
            height: 50px;
            background-color: purple;
        }
    </style>
</head>

<body>
<button>点击紫色盒子才会走</button>
<div></div>
<span></span>
<script>
    function animate(obj, target) {
        //  下面bug解决方法 当我们多次点击按钮的时候 那就先把原先的定时器清除 在开启一个新的定时器
        clearInterval(obj.timer); // 清除定时器
        obj.timer = setInterval(function() {
        if(obj.offsetLeft >= target) {
            clearInterval(obj.timer);
        }
        obj.style.left = obj.offsetLeft + 1 + 'px';
    }, 30);
    }
    var div = document.querySelector('div');
    var span = document.querySelector('span');
    var btn = document.querySelector('button');
    // 调用函数
    animate(div, 300);
    // animate(span, 400);
    btn.addEventListener('click',function() {
        animate(span, 400);  // 当点击按钮才行走
        // 有个bug 当我们不断的点击按钮元素会再次加载一次定时器,再向前行走400的位置,也会越走越快
        // 解决方案 让我们元素只有一个定时器执行
    })
</script>
</body>

缓动动画原理

缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来

思路:
1.让盒子每次移动的距离慢慢变小,速度就会慢慢落下来
2.核心算法:(目标值现在的位置)  /  10      做为每次移动的距离步长

3.停止的条件是:让当前盒子位置等于目标位置就停止定时器

<script>
    function animate(obj, target) {
        //  下面bug解决方法 当我们多次点击按钮的时候 那就先把原先的定时器清除 在开启一个新的定时器
        clearInterval(obj.timer); // 清除定时器
        obj.timer = setInterval(function() {
            //  步长值写在定时器的里面
            var step = (target - obj.offsetLeft) / 10;
            if(obj.offsetLeft == target) {
                clearInterval(obj.timer);
            }
            // 缓动动画 把每次加1 这个步长值改为一个慢慢变小的值  步长公式 (目标值现在的位置)  /  10
            // obj.style.left = obj.offsetLeft + 1 + 'px';
            obj.style.left = obj.offsetLeft + step + 'px';        
        }, 15);  // 一般设置为15
    }
    var div = document.querySelector('div');
    var span = document.querySelector('span');
    var btn = document.querySelector('button');
    // 调用函数
    animate(div, 300);
    // animate(span, 400);
    btn.addEventListener('click',function() {
        animate(span, 500);  // 当点击按钮才行走
        // 有个bug 当我们不断的点击按钮元素会再次加载一次定时器,再向前行走400的位置,也会越走越快
        // 解决方案 让我们元素只有一个定时器执行
    })
</script>

动画函数多个目标值之间移动

可以让动画函数从800移动到500。

当我们点击按钮时候,判断步长是正值还是负值
1.如果是正值,则步长往大了取整

2.如果是负值,则步长向小了取整

<script>
    function animate(obj, target) {
        //  下面bug解决方法 当我们多次点击按钮的时候 那就先把原先的定时器清除 在开启一个新的定时器
        clearInterval(obj.timer); // 清除定时器
        obj.timer = setInterval(function() {
            //  步长值写在定时器的里面
            //  有个bug 因为最后又除不尽的情况 所以移动距离会小于指定的距离
            //  把我们步长值改为整数 不要出现小数的问题  
            //  每次除的时候结果是小数的话往上取整 比如 8.1 取9  用Math.ceil
            //  var step = Math.ceil((target - obj.offsetLeft) / 10);  // 考虑到有后退的过程 这样写的话倒退 取大的数 最终位置会变小
            var step = (target - obj.offsetLeft) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);  // 意为如果是正值就往大了取,如果是负值就往小了取 最后要重新赋值step回来
            if(obj.offsetLeft == target) {
                clearInterval(obj.timer);
            }
            // 缓动动画 把每次加1 这个步长值改为一个慢慢变小的值  步长公式 (目标值现在的位置)  /  10
            // obj.style.left = obj.offsetLeft + 1 + 'px';
            obj.style.left = obj.offsetLeft + step + 'px';        
        }, 15);  // 一般设置为15
    }
    var div = document.querySelector('div');
    var span = document.querySelector('span');
    var btn500 = document.querySelector('.btn500');
    var btn800 = document.querySelector('.btn800');
    // 调用函数
    btn500.addEventListener('click',function() {
        animate(span, 500);
    })
    btn800.addEventListener('click',function() {
        animate(span, 800);
    })
</script>

动画函数添加回调函数

回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调

回调函数写的位置:定时器结束的位置

 1.最后一个函数也可以看作一个实参

 2.最后一个形参callback接受的是实参funciton函数

 3.把这个函数写在定时器结束里面

那么定时器一结束就会判断,有没有传回调函数这个参数,如果有就调用这个callback()函数,如果没有就不执行。

案例全代码:

<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            cursor: default;
        }
        a {
            text-decoration: none;
        }
        div {
            position: absolute;
            left: 0;
            top: 100px;
            width: 100px;
            height: 100px;
            background-color: blue;
        }
        span {
            position: absolute;
            left: 0;
            top: 200px;
            width: 50px;
            height: 50px;
            background-color: purple;
        }
    </style>
</head>

<body>
    <button class="btn500">点击,紫色盒子到500</button>
    <button class="btn800">点击,紫色盒子到800</button>
<div></div>
<span></span>
<script>
    function animate(obj, target, callback) {  // callback 里面存的是一个函数 相当于 callback = 下面的函数funciton(){} 调用的时候 callback()
        //  下面bug解决方法 当我们多次点击按钮的时候 那就先把原先的定时器清除 在开启一个新的定时器
        clearInterval(obj.timer); // 清除定时器
        obj.timer = setInterval(function() {
            //  步长值写在定时器的里面
            //  有个bug 因为最后又除不尽的情况 所以移动距离会小于指定的距离
            //  把我们步长值改为整数 不要出现小数的问题  
            //  每次除的时候结果是小数的话往上取整 比如 8.1 取9  用Math.ceil
            //  var step = Math.ceil((target - obj.offsetLeft) / 10);  // 考虑到有后退的过程 这样写的话倒退 取大的数 最终位置会变小
            var step = (target - obj.offsetLeft) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);  // 意为如果是正值就往大了取,如果是负值就往小了取 最后要重新赋值step回来
            if(obj.offsetLeft == target) {
                // 停止动画 本质是停止定时器
                clearInterval(obj.timer);
                // 回调函数写到定时器结束里面
                // 意为:如果有callback这个函数传过来吗,有就调用callback()这个函数
                if(callback) {
                    // 调用函数
                    callback();
                }
            }
            // 缓动动画 把每次加1 这个步长值改为一个慢慢变小的值  步长公式 (目标值现在的位置)  /  10
            // obj.style.left = obj.offsetLeft + 1 + 'px';
            obj.style.left = obj.offsetLeft + step + 'px';        
        }, 15);  // 一般设置为15
    }
    var div = document.querySelector('div');
    var span = document.querySelector('span');
    var btn500 = document.querySelector('.btn500');
    var btn800 = document.querySelector('.btn800');
    // 调用函数
    btn500.addEventListener('click',function() {
        animate(span, 500);
    })
    btn800.addEventListener('click',function() {
        animate(span, 800, function() {});  // 函数funciton(){} 当一个实参,传递给上面的另外一个函数
    })
</script>
</body>

动画函数封装到单独JS文件里面

因为以后经常使用这个动画函数,可以单独封装到一个js文件里面,使用的时候引用这个JS文件即可。

新建一个animate.js文件夹,将动画函数粘贴进去,然后再引动这个js文件。

<head>
    <style>
        * {
            margin: 0;
            padding: 0;
            cursor: default;
        }
        .sliderbar {
            position: relative;
            left: 92%;
            top: 100px;
        }
        .con {
            position: absolute;
            left: 0;
            top: 0px;
            width: 200px;
            height: 40px;
            background-color: purple;
            color: #fff;
            text-align: center;
            line-height: 40px;
            z-index: -1;
        }
        span {
            position: absolute;
            left: 0;
            top: 0px;
            width: 40px;
            height: 40px;
            background-color: purple;
            color: #fff;
            text-align: center;
            line-height: 40px;
        }
    </style>
    <!-- 引入js动画文件animate -->
    <script src="animate.js"></script>
</head>

<body>
<div class="sliderbar">
    <span>⬅</span>
    <div class="con">问题反馈</div>
</div>
<!-- 引入js动画文件animate -->
<script src="animate.js"></script>
<script>
    // 1.获取元素
    var sliderbar = document.querySelector('.sliderbar');
    var con = document.querySelector('.con');
    // 2.鼠标经过事件
    // 当我们鼠标经过 sliderbar 就会让 con这个盒子滑动到左侧
    sliderbar.addEventListener('mouseenter', function() {
        // 调用animate动画js文件  animate(obj, target, callback);
        animate(con, -160, function() {
            // 当我们动画执行完毕,就把⬅改为→ 
            sliderbar.children[0].innerHTML = '→';
        });
    })
    // 当我们鼠标离开 sliderbar 就会让 con这个盒子滑动到右侧
    sliderbar.addEventListener('mouseleave', function() {
        // 调用animate动画js文件  animate(obj, target, callback);
        animate(con, 0, function() {
            // 当我们动画执行完毕,就把⬅改为→ 
            sliderbar.children[0].innerHTML = '⬅';
        });
    })
</script>

 常见的网页特效案例

案例:网页轮播图

轮播图也称为焦点图,是网页中比较常见的网页特效

功能需求

  • 1.鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
  • 2.点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。
  • 3.图片播放的同时,下面小圆圈模块跟随一起变化。
  • 4.点击小圆圈,可以播放相应图片。
  • 5.鼠标不经过轮播图,轮播图也会自动播放图片。
  • 6.鼠标经过,轮播图模块,自动播放停止。

案例分析:

----------

  • 因为js较多,我们单独新建js文件夹,再新建js文件,引入页面中。
  • 此时需要添加load事件。
  • 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
  • 显示隐藏display按钮。

-----------------

  • 动态生成小圆圈
  • 核心思路:小圆圈的个数要跟图片张数一致
  • 所以首先先得到ul里面图片的张数 (图片放入里面,所以就是li的个数)
  • 利用for循环动态生成小圆圈(这个小圆圈要放入ol里面)
  • 创建节点createElement(‘li’)
  • 插入节点ol.appendChild(li)
  • 第一个小圆圈需要添加current类选中

---------------------------

  • 小圆圈的排他思想
  • 点击当前小圆圈,就添加current类
  • 其余的小圆圈就移除这个current类
  • 注意:我们在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了。

---------------

  • 点击小圆圈滚动图片。
  • 此时用到animate动画函数,将s文件引入 (注意,因为index,js依赖animatejs所以,animate,js 要写到index.js上面)。
  • 使用动画函数的前提,该元素必须有定位
  • 注意是ul移动而不是小li。
  • 滚动图片的核心算法:点击某个小圆圈,就让图片滚动 小圆圈的索引号乘以图片的宽度做为ul移动距离。
  • 此时需要知道小圆圈的索引号,我们可以在生成小圆圈的时候,给它设置一个自定义属性,点击的时候获取这个自定义属性即可。

--------

  • 点击右侧按钮一次,就让图片滚动一张。
  • 声明一个变量num,点击一次,自增1,让这个变量乘以图片宽度,就是ul的滚动距离。
  • 图片无缝滚动原理。
  • 把ul 第一个li复制一份,放到ul 的最后面。
  • 当图片滚动到克隆的最后一张图片时,让ul快速的、不做动画的跳到最左侧:left 为0。
  • 同时num 赋值为0,可以从新开始滚动图片了。

--------

  • 克隆第一张图片
  • 克隆ul 第一个li cloneNode()加true 深克隆复制里面的子节点 false浅克隆
  • 添加到ul 最后面 appendChild

-------

  • 点击右侧按钮,小圆圈跟随变化
  • 最简单的做法是再声明一个变量cirdle,每次点击自增1,注意,左侧按钮也需要这个变量,因此要声明全局变量。
  • 【如果写上面的克隆】图片有7张,我们小圆圈只有6个少一个
  • 必须加一个判断条件如果circle == 6就从新复原为0

-------

 bug-1:当我点击下方第三个小圆点,显示第三张图片;当我再点击右侧按钮的时候应该是第四张图片,但是却显示第二张图片;

原因:下方小圆点是通过num变量来控制,而右按钮是通过li点击来控制,两者没有任何关系。

解决方法:当我们点击了某个小li 就要把这个li 的索引号给 num

-----

bug-2:当我点击 下方第三个小圆点,显示第三张图片;当我再点击右侧按钮的时候是第四张图片,但是小圆点应该是第四个,却显示第二个;

解决方法:当我们点击了某个小li 就要把这个li 的索引号也要给 circle

 ------

  • 自动放功能
  • 添加一个定时器
  • 自动播放轮播图,实际就类似于点击了右侧按钮
  • 此时我们使用手动调用右侧按钮点击事件 arrow_r.click()
  • 鼠标经过focus就停止定时器
  • 鼠标离开focus就开启定时器

 

全部代码:

HTML代码

<div class="focus fl">
    <!-- 左侧按钮 -->
    <a href="javascript:;" class="arrow-l">←</a>
    <!-- 右侧按钮 -->
    <a href="javascript:;" class="arrow-r">→</a>
    <!-- banner底部圆点 -->
    <ol class="circle">
    </ol>
    <!-- 核心的滚动区域 -->
    <ul>
        <li><img src="upload/focus1.png" alt=""></li>
        <li><img src="upload/focus2.png" alt=""></li>
        <li><img src="upload/focus3.png" alt=""></li>
        <li><img src="upload/focus4.png" alt=""></li>
        <li><img src="upload/focus5.png" alt=""></li>
        <li><img src="upload/focus6.png" alt=""></li>
    </ul>
</div>


CSS代码

.focus {
    position: relative;
    float: left;
    width: 721px;
    height: 455px;
    overflow: hidden;
}
.arrow-l,
.arrow-r {
    display: none;
    position: absolute;
    background-color: rgba(0, 0, 0, .5);
    width: 30px;
    height: 50px;
    font-size: 20px;
    color: #fff;
    text-align: center;
    line-height: 50px;
}
.arrow-l {
    top: 220px;
    left: 0;
}.arrow-r {
    top: 220px;
    right: 0;
}
.focus ul {
    position: absolute;
    left: 0;
    top: 0;
    width: 5760px;
    z-index: -2;
    overflow: hidden;
}
.focus ul li {
    float: left;
}
.circle {
    position: absolute;
    bottom: 6px;
    left: 282px;
    width: 178px;
    height: 20px;
    background-color: rgba(255, 255, 255, .1);
    border-radius: 10px;
}
.circle li {
    float: left;
    width: 12px;
    height: 12px;
    background-color: rgba(255, 255, 255, .5);
    border-radius: 6px;
    margin: 3px 8px;
}
.circle .current {
    width: 20px;
    height: 12px;
    background-color: #fff;
}

JS代码(不包括引用的animate.js)

window.addEventListener('load', function() {
    //  获取元素
    var arrow_l = document.querySelector('.arrow-l');
    var arrow_r = document.querySelector('.arrow-r');
    var focus = document.querySelector('.focus');
    //  鼠标经过就显示
    focus.addEventListener('mouseenter',function() {
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
        clearInterval(timer);
        timer = null;  // 清除定时器 给null值
    })
    //  鼠标离开就隐藏
    focus.addEventListener('mouseleave',function() {
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
        timer = setInterval(function(){ // 跟最下面一样 不过不用写var 因为下面已经写过了
            // 手动调用事件
            arrow_r.click();
        }, 2000);
    })
    // 动态生成小圆圈
    var ul = focus.querySelector('ul');
    var ol = focus.querySelector('.circle');
    for(var i = 0; i < ul.children.length; i++) {
        // 创建一个li
        var li = this.document.createElement('li');
        // 记录当前小圆圈的索引号 通过自定义属性来做  setAttribute 设置属性值
        li.setAttribute('index', i);
        // 把li插入到ol里面
        ol.appendChild(li);
        // 小圆圈的排他思想 我们可以直接再生成小圆圈的同时直接绑定点击事件
        li.addEventListener('click', function() {
            // 干掉所有人 把所有的li 清除current 类名
            for(var i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';
            }
            // 留下我自己 当前的li 设置current 类名
            this.className = 'current';
            // 点击小圆圈 移动图片 移动的是 ul
            // ul 的移动距离 小圆圈的索引号 乘以 图片的宽度 注意是负值
            // 当我们点击了某个li 就拿到当前li的索引号
            var index = this.getAttribute('index');
            // 当我们点击了某个小li 就要把这个li 的索引号给 num
            num = index;
            // 当我们点击了某个小li 就要把这个li 的索引号给 circle
            circle = index;  // 连写 num = circle = index;
            var li = ul.querySelector('li');
            var focusWidth = li.offsetWidth;  // 图片的宽度
            animate(ul, - index * focusWidth);
        })
    }
    // 把ol里面的第一个li设置类名为 current
    ol.children[0].className = 'current';
    // 克隆第一张图片(li)放到ul最后面【如果下if里面num = -1;就不用写这步克隆】
    //  var first = ul.children[0].cloneNode(true);
    //  ul.appendChild(first);
    // 点击左侧或右侧按钮 图片滚动一张
    var num = 0;
    var focusWidth1 = focus.offsetWidth;  // 图片的宽度
    // 控制小圆圈的播放
    var circle = 0;
    // 点击右侧按钮 图片向右滚动
    arrow_r.addEventListener('click', function() {
        // 如果走到了最后复制的一张图片,因此 我们的ul 要快速复原 left 改为0
        if(num == ul.children.length - 1) {
            ul.style.left = 0;
            num = -1;  // 如果num = 0;就是需要最后复制第一个 点击后直接到第二个
        }
        num++;  // 每点击一次 num加一
        animate(ul, - num * focusWidth1);
        // 点击右侧按钮 底部小圆圈一起跟着图片的变动而变动 可以再声明一个变量控制小圆圈的播放        
        circle++;
        // 如果circle == 6 说明走到最后这张图片了 我们就复原
        if (circle == ol.children.length) {
            circle = 0;
        }
        // 先清除其余小圆圈的current类名
        for(var i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        }
        // 留下当前的小圆圈的current类名
        ol.children[circle].className = 'current';
    });
    // 点击左侧按钮 图片向左滚动
    arrow_l.addEventListener('click', function() {
        // 如果第一张图片再按左键,因此 我们的ul 要快速到最后一个
        if(num == 0) {
            ul.style.left = - (ul.children.length - 1) * focusWidth1 + 'px';
            num = ul.children.length;
        }
        num--;  // 每点击一次 num减一
        animate(ul, - num * focusWidth1);
        // 点击右侧按钮 底部小圆圈一起跟着图片的变动而变动 可以再声明一个变量控制小圆圈的播放        
        circle--;
        // 如果circle < 0 说明是在第一张图上 再点击左侧按钮 则小圆圈要改为最后一个小圆圈
        if (circle < 0) {
            circle = ol.children.length - 1;
        }
        // 先清除其余小圆圈的current类名
        for(var i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        }
        // 留下当前的小圆圈的current类名
        ol.children[circle].className = 'current';
    });
    // 自动播放功能
    var timer = this.setInterval(function() {
        // 手动调用点击事件
        arrow_r.click();
    }, 2000);

节流阀

防止轮播图按钮连续点击造成播放过快。

节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发

核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数

  • 开始设置一个变量var flag=true;
  • If(flag) {flag= false; do something}  关闭水龙头
  • 利用回调函数动画执行完毕,flag= true 打开水龙头

声明变量,右侧按钮 关闭节流阀

右侧按钮 打开节流阀 

 左侧按钮 关闭节流阀

  左侧按钮 打开节流阀

 回调函数写法:

第一种写法

if(callback) {

        // 调用函数

          callback();

}

回调函数写到定时器结束里面

意为:如果有callback这个函数传过来吗,有就调用callback()这个函数

----

第二种写法  更高级 简洁

callback && callback();

运用了前面的逻辑与的短路运算 都是true 才会运行

意为如果callback 为真(true) 则调用后面的   callback()函数

如果callback 为假(false) 则直接跳过结束了  后面的不执行

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值