JavaScript 网页编程(三)——Web API(BOM)

一.BOM 简介

  • ​BOM(Browser Object Model)浏览器对象模型:提供与浏览器窗口进行交互的对象
  • JavaScript 语法标准化是 ECMA,DOM 标准化是 W3C
  • BOM 是 Netscape 浏览器标准的一部分,没有标准兼容性差
  • BOM 包含 DOM
  • DOM 把 文档 当对象看,BOM 把 浏览器 当对象看
  • DOM 的顶级对象是 document BOM 的顶级对象是 window
  • DOM 学习操作页面元素,BOM 学习与浏览器窗口交互的对象
  • 注意:window下的一个特殊属性 window.name
  • window对象 包含:document、location、navigation、screen、history

二.window 对象的常见事件

1.页面(窗口)加载事件

  • 有了页面(窗口)加载事件,就可以把 JS 写到页面元素的上方
  • window.onload :文档内容 完全加载完时,触发事件
  • DOMContentLoaded(​IE9以上才支持) DOM 加载完成时触发事件,不包括 样式表,图片,flash 等
  • 如果页面图片多,用 DOMContentLoaded 合适

2.调整窗口大小事件

  • window.onresize 调整窗口大小事件:只要 窗口大小 发生像素变化,就会触发事件,响应式布局
  • window.innerWidth :当前屏幕的宽度
        window.addEventListener('load', function() { // 注册页面加载事件
            var div = document.querySelector('div');
            window.addEventListener('resize', function() { // 注册调整窗口大小事件
                if (window.innerWidth <= 800) { // window.innerWidth 获取窗口大小
                    div.style.display = 'none';
                } else { // 实现响应式布局
                    div.style.display = 'block';
                }})})

3.定时器

3.1 setTimeout() 炸弹定时器

  • 语法规范:setTimeout(调用函数, 延时时间);
  • 清除 setTimeout() 定时器:clearTimeout(定时器名字);
  • 延时时间单位是毫秒 可以省略,如果省略默认的是0
  • 页面中可能有很多的定时器,给定时器 加名字
       
       
       var btn = document.querySelector('button');
       var ad = document.querySelector('.ad'); 
       var timer = setTimeout(function() { // 设置定时器,5s后关闭广告,定时器叫 timer
             ad.style.display = 'none';
       }, 5000);
       btn.addEventListener('click', function() { // 点击后清除定时器
            clearTimeout(timer); // 清除定时器    })

3.2 setInterval() 闹钟定时器

  • 语法规范:setInterval(调用函数, 延时时间);
  • setTimeout():延时时间到了,就调用回调函数,只调用一次
  • setInterval():每隔一个延时时间,就调用回调函数,调用很多次
  • 京东购物倒计时分析:
  1. 获取时分秒盒子,用户输入目标时间总毫秒数,开启定时器,每隔一秒调用一次
  2. 开启定时器之前,先调用一次 countDown(),防止第一次刷新页面有空白
  3. 获取当前和目标时间的时间差,将毫秒格式化
        var hour = document.querySelector('.hour');         // 小时盒子
        var minute = document.querySelector('.minute');     // 分钟盒子
        var second = document.querySelector('.second');     // 秒数盒子
        var inputTime = +new Date('2020-4-1 18:00:00');     // 用户输入时间总的毫秒数
        countDown();     // 先调用一次这个函数,防止第一次刷新页面有空白 
        // 2. 开启定时器
        setInterval(countDown, 1000); // 每隔一秒调用一次定时器 更新显示时间
        function countDown() {
            var nowTime = +new Date(); // 当前时间总的毫秒数
            var times = (inputTime - nowTime) / 1000; // times 是剩余时间总的秒数 
            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;
            second.innerHTML = s;
        }
  • 发送短信倒计时分析:
  1. 设置剩余秒数为全局变量,发送后禁用按钮,并开启定时器,每秒调用一次
  2. 当剩余秒数为零,则清除定时器,恢复按钮文字和禁用状态
  3. 当前剩余秒数不为零,则每一次调用后 剩余时间都要-1(time--) 并显示在按钮上
    手机号码:  
        var btn = document.querySelector('button');
        var time = 60; 		// 全局变量,定义剩下的秒数
        btn.addEventListener('click', function() {
            btn.disabled = true; // 点击一次后,禁用按钮
            var timer = setInterval(function() { // 设置定时器
                if (time == 0) {     // 清除定时器 和 复原按钮
                    clearInterval(timer);
                    btn.disabled = false;
                    btn.innerHTML = '发送';
                } else {
                    btn.innerHTML = '还剩下' + time + '秒';
                    time--;    // 每隔一秒调用一次定时器 相应的剩余时间也在减少
                }}, 1000);});

 

三.location 、navigator、history 对象

1.location对象

  • location对象:用于 获取 / 设置 / 解析 窗体URL
  • location对象 常见属性:
  1. location.href = 'http://...'; :获取 / 设置整个 URL
  2. location.search:获取页面参数(网址后面部分)
  • location对象 常见方法:
  1. location.assign('http://...');:类似 href 跳转页面,记录浏览历史,可以 实现后退功能
  2. location.replace('http://...');页面替换,不记录浏览历史,不可以 实现后退功能
  3. location.reload(true);:重新加载页面(true = 强制刷新)
  • URL格式:protocol://host[:port]/path/[?query]#fragment
  • Example:http://www.index.cn/index.html?name=tea&age=20#link
  • protocol:通信协议;host:主机域名;prot:可选端口号;query:参数;fragment:链接锚点
  • 自动获取用户名的欢迎登陆页面分析:
  • 得到页面参数 location.search,参数去掉? substr(),字符串参数转为数组 split(),对应信息写入页面
// login.html 负责书写 表单要提交的内容   
 
// 提交到 index.html 页面 用户名: // 名字用 uname 存储
// index.html 负责接收 提交表单 的页面 console.log(location.search); // ?uname=andy var params = location.search.substr(1); // uname=andy substr('起始位置',截取字符数); var arr = params.split('='); // 把字符串分割为数组,得到["uname", "ANDY"] var div = document.querySelector('div'); div.innerHTML = arr[1] + '欢迎您'; // 把数据写入div中

2.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 = ""; // 电脑}

3.history对象

  • history对象:与浏览器 历史记录 进行交互
  • history对象 实际开发中比较少用,会在 OA办公系统 见到
  • 前进: history.forward(); = history.go(1);
  • 后退: history.back(); = history.go(-1);

四.JS执行机制 和 this指向问题

1.JS执行机制、单线程、同步异步

  • 问题: JS 执行的时间过长,会造成页面的渲染不连贯
  • 同步任务:在 主线程上 排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
  • 异步任务不进入主线程、而 进入”任务队列” 的任务,当主线程中的任务运行完了,才会 从”任务队列”取出 异步任务 放入 主线程 执行
  • 一般而言,异步任务有以下三种类:
  1. 普通事件:如 click、resize 等
  2. 资源加载:如 load、error 等
  3. 定时器:包括 setInterval、setTimeout 等

2.this 指向问题

  • ​this 的指向在 函数定义时 确定不了,函数执行时 才能确定,一般情况下,this 最终指向的是 调用它的对象
  1. 全局作用域或者普通函数中 this 指向 全局对象window(注意定时器里面的 this 指向 window
  2. 方法调用中 谁调用 this 指向谁
  3. 构造函数中 this 指向 实例
    
    

 

五.元素偏移量 offset 系列

1.offset 概述

  • offset (偏移量)动态的 得到元素的位置(偏移)、大小等
  • 获得元素距离 带有定位(重要!!) 父元素的位置
  • 获得元素 自身大小(宽度高度)
  • 返回值 不带单位
  • JavaScript 网页编程(三)——Web API(BOM)_第1张图片

2.offset 与 style 区别

  • offset 可以得到 任意样式表的 样式值,style 只能得到 行内样式表的 样式值
  • offset 返回数值 没有单位,style 返回字符串 有单位
  • offsetWidth 包含 padding+border+width,style.width 不包含 padding+border
  • offsetWidth 是只读属性,style.width 是可读写属性
  • 获取元素大小位置,用 offset 更合适;给元素更改值,用 style 改变

3.获取鼠标在盒子内的坐标

  • 在盒子内点击,想要得到鼠标 距离盒子左右(不是距离浏览器页面) 的距离。
  • 首先得到 鼠标在页面中的 坐标(e.pageX, e.pageY)
  • 其次得到 盒子在页面中 的坐标 ( box.offsetLeft, box.offsetTop)
  • 鼠标在页面中的坐标 - 盒子在页面中的坐标 = 鼠标在盒子内的坐标
  • 移动鼠标,会改变 鼠标在页面中的 坐标,而 盒子在页面中的 坐标不变,因此 鼠标在盒子内的 坐标改变
  • 弹出框(模态框)拖拽效果:JavaScript 网页编程(三)——Web API(BOM)_第2张图片
  1. 点击弹出按钮,会弹出模态框, 并显示灰色半透明遮挡层,display:block;
  2. 点击关闭按钮,会关闭模态框,同时关闭灰色半透明遮挡层,display:none;
  3. 拖拽原理:鼠标 按下mousedown 并且 移动mousemove, 之后鼠标松开mouseup
  4. 拖拽过程:鼠标按住模态框最上层,移动时获得最新的值,赋给模态框的 left 和 top 值
  5. 模态框位置 = 鼠标页面坐标 - 鼠标盒子内坐标,拖拽过程中 鼠标盒子内坐标不变(即移动松开 写到 按下 里面)
  6. 鼠标按下,得到 鼠标在盒子内的坐标 = 鼠标页面坐标 - 模态框页面坐标
  7. 鼠标移动,把 模态框页面坐标  设置为  :鼠标页面坐标 - 鼠标盒子内坐标
  8. 鼠标松开,停止拖拽,即解除鼠标移动事件
        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', 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);
            })
        })

5.京东放大镜

  • 分为三个模块:鼠标经过小图片, 遮挡层和大图片显示,离开则隐藏;遮挡层跟随鼠标;大图片跟随遮挡层移动
  • 遮挡层跟随鼠标:获得鼠标在盒子的坐标,把数值给 遮挡层 作为 left 和 top 值,遮挡层不能超出小图片盒子范围
  • 遮挡层需要减去自身高度和宽度的一半,实现鼠标在遮挡层中间的效果
  • 遮挡层的最大移动距离 = 小图片盒子宽度 - 遮挡层盒子宽度
  • 大盒子的最大移动距离 = 大图片自身宽度 -  大图片盒子宽度
  • 遮挡层移动距离:遮挡层最大距离 = 大图片移动距离:大图片最大移动距离,由此得到 大图片移动距离
window.addEventListener('load', function() {    // 页面加载完后执行js 否则无法获取元素
    var preview_img = document.querySelector('.preview_img');    // 预览小盒子
    var mask = document.querySelector('.mask');    // 遮挡层
    var big = document.querySelector('.big');    // 放大镜大盒子

    // 1. 鼠标经过 preview_img 预览小盒子 就显示和隐藏 mask 遮挡层 和 big 大盒子
    preview_img.addEventListener('mouseover', function() {
        mask.style.display = 'block';
        big.style.display = 'block';    })
    preview_img.addEventListener('mouseout', function() {
            mask.style.display = 'none';
            big.style.display = 'none';    })

    // 2. 鼠标移动,遮挡层跟着鼠标走
    preview_img.addEventListener('mousemove', function(e) {    // 这种侦听器 可以给同一对象定义多个事件去执行
        // (1). 鼠标在盒子内的坐标
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        // (2) 鼠标在盒子内的坐标 - 盒子高宽的一半 = mask 最终的 left 和 top 值
        // (3) mask 移动的距离 减法相当于遮挡层 mask 左移和上移
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y - mask.offsetHeight / 2;
        // (4) 如果移动距离 maskX < 0 就停在 0 的位置
        // 遮挡层的最大移动距离 = 预览小图片盒子宽度 - 遮挡层盒子宽度
        var maskMax = preview_img.offsetWidth - mask.offsetWidth;
        if (maskX <= 0) {
            maskX = 0;
        } else if (maskX >= maskMax) {
            maskX = maskMax;}
        if (maskY <= 0) {
            maskY = 0;
        } else if (maskY >= maskMax) {
            maskY = maskMax;}
        mask.style.left = maskX + 'px';
        mask.style.top = maskY + 'px';

        // 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
        var bigIMg = document.querySelector('.bigImg');    // 大图
        // 大图片最大移动距离 = 大图本身的宽度 - 大图盒子的宽度
        var bigMax = bigIMg.offsetWidth - big.offsetWidth;
        // 大图片的移动距离 X Y
        var bigX = maskX * bigMax / maskMax;
        var bigY = maskY * bigMax / maskMax; 
        bigIMg.style.left = -bigX + 'px';    // 遮挡层 和 大图 移动方向是相反的
        bigIMg.style.top = -bigY + 'px';
    })
})
  • 效果展示:JavaScript 网页编程(三)——Web API(BOM)_第3张图片

六.元素可视区 client 系列

1.client 概述

  • client(客户端):动态得到元素的 边框大小、元素大小 等。
  • clientWidth 和 offsetWidth 最大的区别:不包含边框。

  • JavaScript 网页编程(三)——Web API(BOM)_第4张图片

  • JavaScript 网页编程(三)——Web API(BOM)_第5张图片

2.flexible.js 

2.1 立即执行函数

  • 主要作用: 创建一个独立的作用域。 避免了命名冲突问题
  • (function(){})()  :参数可有可无(这个比较好理解),记得结尾加分号
        (function(a, b) {
            console.log(a + b);
        })(1, 2); // 第二个小括号可以看做是调用函数
  • ​​​​​​(function(){}()):参数可有可无,记得结尾加分号
 (function sum(a, b) {
            console.log(a + b);
            var num = 10; // 局部变量
        }(2, 3));

2.2 pageshow 事件

  • 下面三种情况 刷新页面 都会触发 load 事件:
  1. a标签的超链接
  2. F5或者刷新按钮(强制刷新)
  3. 前进后退按钮
  • 问题:火狐浏览器,有“往返缓存”,保存着页面数据 + DOM 、JavaScript 的状态,此时后退 不能 刷新页面
  • 解决方案:使用 pageshow 事件(重新加载页面触发的事件这个事件给 window 添加
  • e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新......
     window.addEventListener('pageshow', function(e) {
        // e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新计算 rem 大小
        if (e.persisted) {
            setRemUnit()}     })

2.3 mouseover / mouseout VS mouseenter / mouseleave

  • mouseover / mouseout:鼠标经过自身盒子会触发,经过子盒子还会触发
  • mouseenter / mouseleave:鼠标经过自身盒子触发
  • mouseenter(鼠标经过) mouseleave(鼠标离开):这两个事件不会冒泡,所以不触发

2.4 淘宝 flexible.js 源码分析

(function flexible(window, document) {    // 立即执行函数
    var docEl = document.documentElement    // 获取的 html 的根元素
    var dpr = window.devicePixelRatio || 1    // dpr 物理像素比

    function setBodyFontSize() {    // 设置 body 的字体大小
        if (document.body) {    // 如果页面中有 body 元素 就设置 body 字体大小
            document.body.style.fontSize = (12 * dpr) + 'px'
        } else {
            // 如果页面中没有 body 元素,则等页面 DOM 元素加载完毕 再设置 body 字体大小
            document.addEventListener('DOMContentLoaded', setBodyFontSize)    }}
    setBodyFontSize();

    function setRemUnit() {    // 设置 html 元素的文字大小
        var rem = docEl.clientWidth / 10    // clientWidth:不包括边框 border
        docEl.style.fontSize = rem + 'px'    }
    setRemUnit()

    // 当页面尺寸大小发生变化时,重新设置 rem 大小
    window.addEventListener('resize', setRemUnit)
    // pageshow:重新加载页面触发的事件
    window.addEventListener('pageshow', function(e) {
        // e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新计算 rem 大小
        if (e.persisted) {
            setRemUnit()}     })

    // 有些移动端的浏览器 不支持0.5像素 的写法
    if (dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.border = '.5px solid transparent'
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
            docEl.classList.add('hairlines')     }
        docEl.removeChild(fakeBody)     }
}(window, document))

 

七.元素滚动 scroll 系列

1.scroll 概述

  • scoll(滚动的):动态得到元素的 元素大小、滚动距离 等。
  • JavaScript 网页编程(三)——Web API(BOM)_第6张图片
  • JavaScript 网页编程(三)——Web API(BOM)_第7张图片

2.页面被卷去的头部

  • 浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条,滚动触发 onscroll 事件。
  • 页面上因滚动而被隐藏的高度,称为页面被卷去的头部。

仿淘宝固定右侧侧边栏 + 返回顶部:

  • 原先侧边栏是绝对定位,当页面滚动到一定位置,侧边栏改为固定定位
  • 页面继续滚动,会让 返回顶部 显示出来
  • 是页面滚动,所以事件源是 document
  • 页面被卷去的头部:可以通过 window.pageYOffset 获得  被卷去的左侧:window.pageXOffset
  • 区分:元素被卷去的头部:element.scrollTop  , 页面被卷去的头部: window.pageYOffset
        //1. 获取元素
        var sliderbar = document.querySelector('.slider-bar');    // 侧边栏
        var banner = document.querySelector('.banner');
        // banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面
        var bannerTop = banner.offsetTop
        var sliderbarTop = sliderbar.offsetTop - bannerTop;    // 侧边栏 固定定位之后 应该变化的数值
        var main = document.querySelector('.main');    // 主体部分
        var goBack = document.querySelector('.goBack');    // 返回顶部
        var mainTop = main.offsetTop;

        // 2. 页面滚动事件 scroll
        document.addEventListener('scroll', function() {
            // 3 .当页面被卷去的头部 >= bannerTop 时 侧边栏改为固定定位
            if (window.pageYOffset >= bannerTop) {
                sliderbar.style.position = 'fixed';
                sliderbar.style.top = sliderbarTop + 'px';
            } else {
                sliderbar.style.position = 'absolute';
                sliderbar.style.top = '300px';    }
            // 4. 当页面滚动到 main 主体部分,就显示 goback模块
            if (window.pageYOffset >= mainTop) {
                goBack.style.display = 'block';
            } else {
                goBack.style.display = 'none';     }
            // 5. 点击返回顶部模块,就让窗口滚动的页面的最上方
        goBack.addEventListener('click', function() {
            // 里面的 x 和 y 不跟单位的 直接写数字即可
            // window.scroll(0, 0);
            animate(window, 0);    // 因为是窗口滚动 所以对象是 window    })

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

  • 页面被卷去的头部,有兼容性问题,解决方案如下:
function getScroll() {
    return {
      left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0,
      top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0 };} 
使用的时候  getScroll().top

3.offset、client、scroll 总结

  • offset:获得元素位置    offsetLeft  offsetTop
  • client:获取元素大小  clientWidth clientHeight
  • scroll:获取滚动距离 scrollTop  scrollLeft(页面滚动的距离通过 window.pageXOffset  获得)
  • 区分:页面滚动的距离通过 window.pageXOffset 获得
  • JavaScript 网页编程(三)——Web API(BOM)_第8张图片

八.动画函数封装

1.动画实现原理

  • 核心原理:通过定时器 setInterval() 不断移动盒子位置。
  • 实现步骤:
  1. 获得盒子当前位置
  2. 利用定时器不断重复:让盒子在当前位置加上1个移动距离
  3. 加一个结束定时器的条件
  4. 元素需要添加定位,才能使用 element.style.left
  • 封装动画函数,给不同元素记录不同定时器原理:
  • 声明动画函数:function animate(目标对象, 目标位置) {内部逻辑代码}
  • 调用动画函数:animate(目标对象, 目标位置);
  • 不断的点击按钮,元素动画速度会越来越快,因为开启了太多的定时器
  • 解决方案: 清除以前的定时器,让元素只有一个定时器执行:clearInterval(obj.timer);
        // 简单动画函数封装:obj = 目标对象 target = 目标位置
        function animate(obj, target) {
            // 不断的点击按钮,span 的速度会越来越快,因为开启了太多的定时器
            // 解决方案: 清除以前的定时器,让元素只有一个定时器执行
            clearInterval(obj.timer);
            var timer = setInterval(function() {
                if (obj.offsetLeft >= target) {
                    clearInterval(timer);    // 停止动画 本质是停止定时器
                }
                obj.style.left = obj.offsetLeft + 1 + 'px';    // 记得定位 才能写 left 
            }, 30);
        }
        var btn = document.querySelector('button');
        var span = document.querySelector('span');
        // 调用函数
        btn.addEventListener('click', function() {    // 点击按钮后 给 span 调用动画函数
            animate(span, 200);
        })

3.缓动效果原理

  • 缓动动画就是让元素运动速度改变,最常见的是让速度慢慢降下来,思路如下:
  1. 让盒子 每次移动的距离 慢慢变小,速度就会慢慢落下来。
  2. 核心算法: (目标值 - 现在的位置)   /  10    = 每次移动的距离步长(需要取整)
  3. 停止条件: 盒子位置 = 目标位置 就停止定时器
  • 使用动画函数在 多个目标值之间移动 思路如下:
  1. 让动画函数从 800 移动到 500,当点击按钮时候,判断步长是正值还是负值
  2. ​如果是正值,则步长 往大了取整
  3. ​如果是负值,则步长 向小了取整

4.动函数添加回调函数

  • 将函数作为参数传到动画函数里面,当动画函数执行完之后,再执行参数函数,这个过程就叫做回调。
  • 回调函数写的位置:定时器结束的位置。
  • 比如给动画函数添加回调函数,那么执行完动画之后会执行回调函数里的代码,比如:
                animate(span, 800, function() {
                    span.style.backgroundColor = 'red';    // 先移动到 800px 处,再背景变红
                });

5.动画函数完整版代码

function animate(obj, target, callback) {
    // 清除以前的定时器,只保留当前定时器执行
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        // 步长值写到定时器的里面,进行 取整 操作,步长公式:(目标值 - 现在位置) / 10
        var step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (obj.offsetLeft == target) {
            clearInterval(obj.timer);    // 停止动画 本质是停止定时器
            callback && callback();    // 回调函数写到定时器里面
        }
        obj.style.left = obj.offsetLeft + step + 'px';
    }, 15);
}

引用 animate 动画函数:

 
 
问题反馈

 

九.常见网页特效

1.轮播图

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

思路分析:

  ① js较多,单独建 js 文件夹,建js文件,引入页面
② 需要添加 load 事件
③ 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
 
1.1 动态生成小圆圈
② 核心思路:小圆圈的个数要跟图片张数一致
③ 所以先得到 ul  里面图片的张数(图片放入  li  里面,所以就是  li 的个数)
④ 利用循环动态生成小圆圈(这个小圆圈要放入 ol 里面)
⑤ 创建节点 createElement(‘li’)
⑥ 插入节点 ol. appendChild(li)
⑦ 第一个小圆圈需要添加 current 类  
1.2 小圆圈的排他思想
② 点击当前小圆圈,就添加  current 
③ 其余的小圆圈就移除这个  current 
④ 注意: 刚才生成小圆圈的同时,可以直接绑定点击事件
1.3 点击小圆圈滚动图
② 此时用到  animate 动画函数, 引入js 文件(因为index.js 依赖 animate.js 所以, animate.js 要写到 index.js 上面)
③ 使用动画函数的前提,该元素必须有定位
④ 注意是  ul 移动 而不是小  li
⑤ 滚动图片的核心算法: 点击某个小圆圈 , 让图片滚动 小圆圈的索引号 x 图片的宽度 做为  ul  移动距离
⑥ 此时需要知道小圆圈的索引号,可以在生成小圆圈时,设置自定义属性,点击的时候获取自定义属性即可
1.4 点击右侧按钮一次,就让图片滚动一张
② 声明变量  num , 点击一次,自增 1 , 让 num x 图片宽度,就是 ul 的滚动距离
③ 图片无缝滚动原理
④ 把 ul 第一个 li 复制一份,放到 ul 的最后面
⑤ 当图片滚动到克隆的最后一张图片时, 让  ul 快速的、不做动画的跳到最左侧: left 为  0
⑥ 同时  num 赋值为  0 ,重新开始滚动图片了
1.5 克隆第一张图片
② 克隆  ul 第一个 li cloneNode() 加  true 深克隆 复制里面的子节点 false 浅克隆
③ 添加到 ul 最后面 appendChild
1.6 点击右侧按钮, 小圆圈跟随变化
② 声明变量  circle ,每次点击自增 1 ,注意,左侧按钮也需要这个变量,因此要声明全局变量
③ 图片有  张,小圆圈只有  个,少一个,必须加一个判断条件
④ 如果 circle == 4 就重新复原为 0
1.7 自动播放功能
② 添加定时器
③ 自动播放轮播图,实际就类似于点击了右侧按钮
④ 此时使用手动调用右侧按钮点击事件 arr ow_r.click()
⑤ 鼠标经过 focus 就停止定时器
⑥ 鼠标离开 focus 就开启定时器
window.addEventListener('load', function() {
    // 1. 获取元素
    var arrow_l = document.querySelector('.arrow-l');    // 左按钮
    var arrow_r = document.querySelector('.arrow-r');    // 右按钮
    var focus = document.querySelector('.focus');    // 焦点图盒子
    var focusWidth = focus.offsetWidth;    // 焦点图盒子宽度
    // 2. 鼠标经过 focus 显示隐藏左右按钮 同时停止定时器 阻止自动播放
    focus.addEventListener('mouseenter', function() {
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
        clearInterval(timer);
        timer = null;    // 清除定时器变量
    });
    focus.addEventListener('mouseleave', function() {
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
        timer = setInterval(function() {    // 鼠标离开 focus 开始自动播放 相当于手动调用点击事件
            arrow_r.click();    // 每 2s 执行一次 右按钮单击事件
        }, 2000);
    });
    // 3. 动态生成小圆圈:有几张图片,就生成几个小圆圈
    var ul = focus.querySelector('ul');
    var ol = focus.querySelector('.circle');
    for (var i = 0; i < ul.children.length; i++) {
        var li = document.createElement('li');    // 创建一个 li 
        li.setAttribute('index', i);    // 记录当前小圆圈的索引号 通过自定义属性来做 
        ol.appendChild(li);    // 把 li 插入到 ol 里面
        // 4. 小圆圈排他思想 生成小圆圈的同时 直接绑定 点击事件
        li.addEventListener('click', function() {
            // 干掉所有人 把所有 li 清除 current 类名
            for (var i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';}
            // 留下我自己  当前 li 设置current 类名
            this.className = 'current';
            // 5. 点击小圆圈,移动图片 移动的是 ul 
            // ul 的移动距离 = 被点击的小圆圈的索引号 x 图片的宽度 注意向左移动是负值
            var index = this.getAttribute('index');
            num = index;    // 点击某个 li 就把 这个li 的索引号 给 num  
            circle = index;    // 点击某个 li 就把 这个li 的索引号 给 circle  
            animate(ul, -index * focusWidth);    // 给 ul 做移动动画
        })
    }
    ol.children[0].className = 'current';    // 把 ol 的第一个 li 设置类名为 current
    // 6. 克隆 第一张图片(li)放到 ul 最后面,true 表示克隆包括内容
    var first = ul.children[0].cloneNode(true);
    ul.appendChild(first);
    var num = 0;    // 7. 点击右侧按钮, 图片滚动一张
    var circle = 0;    // circle 控制小圆圈的播放
    var flag = true;    // flag 节流阀:防止没有播放完移动动画 就直接进行下一次移动动画
    arrow_r.addEventListener('click', function() {
        if (flag) {
            flag = false; // 关闭节流阀
            // 走到最后复制的一张图片,此时 ul 要快速复原 left 改为 0
            if (num == ul.children.length - 1) {
                ul.style.left = 0;
                num = 0;
            }
            num++;
            animate(ul, -num * focusWidth, function() {    // 用回调函数操控节流阀
                flag = true; // 打开节流阀
            });
            // 8. 点击右侧按钮,小圆圈跟随一起变化
            circle++;            
            if (circle == ol.children.length) {    // 走到最后克隆的图片 复原
                circle = 0;
            }           
            circleChange();     // 调用函数
        }
    });
    // 9. 左侧按钮做法
    arrow_l.addEventListener('click', function() {
        if (flag) {
            flag = false;
            if (num == 0) {
                num = ul.children.length - 1;
                ul.style.left = -num * focusWidth + 'px';
            }
            num--;
            animate(ul, -num * focusWidth, function() {
                flag = true;
            });
            // 点击左侧按钮,小圆圈跟随一起变化 
            circle--;
            circle = circle < 0 ? ol.children.length - 1 : circle;
            circleChange();
        }
    });
    function circleChange() {
        for (var i = 0; i < ol.children.length; i++) {    // 清除小圆圈的 current 类名
            ol.children[i].className = '';
        }
        ol.children[circle].className = 'current';     // 留下当前小圆圈的 current 类名
    }
    // 10. 自动播放轮播图
    var timer = setInterval(function() {
        arrow_r.click();    // 手动调用点击事件
    }, 2000);
})

2.节流阀

  • 防止 轮播图按钮 连续点击 造成 播放过快。
  • 节流阀目的:上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法 连续触发。
  • 核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
  • 开始设置一个变量 var flag= true;
  • If(flag){flag = false; do something}       关闭水龙头
  • 利用回调函数动画执行完毕, flag = true     打开水龙头

2.1 返回顶部

  • 带有动画的返回顶部,是页面滚动,使用 window.scroll(x,y),里面的 x 和 y 不跟单位的 直接写数字即可
        // 点击返回顶部模块,就让窗口滚动的页面的最上方
        goBack.addEventListener('click', function() {
            // 里面的 x 和 y 不跟单位的 直接写数字即可
            // window.scroll(0, 0);
            animate(window, 0);    // 因为是窗口滚动 所以对象是 window
        }

2.2 筋斗云导航栏

  • 筋斗云的 起始位置 是 0
  • 鼠标经过某个 li,把当前 li 的offsetLeft  作为为目标值(云到鼠标经过的导航栏)
  • 鼠标离开某个 li,把目标值设为 0(云回第一个导航栏)
  • 点击了某个 li, 把 li 当前的位置 存储起来,作为筋斗云的 起始位置
window.addEventListener('load', function() {
            var cloud = document.querySelector('.cloud');    // 筋斗云
            var c_nav = document.querySelector('.c-nav');    // 导航栏
            var lis = c_nav.querySelectorAll('li');    // 导航栏元素
            var current = 0;    // current 作为筋斗云的起始位置
            for (var i = 0; i < lis.length; i++) {
                // (1) 鼠标经过 把当前 li 的位置做为目标值
                lis[i].addEventListener('mouseenter', function() {
                    animate(cloud, this.offsetLeft);
                });
                // (2) 鼠标离开 回到起始的位置 
                lis[i].addEventListener('mouseleave', function() {
                    animate(cloud, current);
                });
                // (3) 鼠标点击 把当前位置作为目标值
                lis[i].addEventListener('click', function() {
                    current = this.offsetLeft;
                });}})

 

十. 触屏事件

1.触屏事件(Touch)、触摸事件对象(TouchEvent)

  • 触屏(触摸)事件 touch:touch 对象代表一个触摸点(手指 / 触摸笔)
  • TouchEvent 包含:touchstart、touchmove、touchend 
  • touches:正在 触摸屏幕的 所有手指的列表 
  • targetTouches:正在 触摸当前 DOM 元素的 手指列表,给元素注册触摸事件,所以重点记住 targetTocuhes
  • 如果侦听的是一个 DOM 元素,touches = TargetTouches
  • targetTouches[0]:正在触摸 DOM 元素的第一个手指的相关信息,比如 手指的坐标 e.targetTouches[0]
  • changedTouches:手指状态发生了改变的列表 从无到有 或者 从有到无
  • 当手指离开屏幕的时候,就没有了 touches 和 targetTouches 列表,但是会有 changedTouches

3.移动端拖动元素

  • touchstart、touchmove、touchend,实现拖动元素
  • 拖动元素需要手指的坐标值,使用  targetTouches[0] 里面的 pageX 和 pageY
  • 拖动元素三步曲:
  1. 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
  2. 移动手指 touchmove: 计算手指的滑动距离,并且改变盒子的 left、top
  3. 离开手指 touchend:
  • 注意: 手指移动也会 触发滚动屏幕 所以要 阻止默认的屏幕滚动 e.preventDefault();
    

4.classList 属性

  • classList 属性:返回元素的类名,所有类名都不带点IE10 以上版本支持。
  • 添加类:element.classList.add(’类名’);在后面追加类名 不会 覆盖以前的类名
  • 移除类:element.classList.remove(’类名’);
  • 切换类:element.classList.toggle(’类名’);

5.click 延时解决方案

  • 移动端 click 事件会有 300ms 的延时,原因是移动端双击 会缩放页面,解决方案有三种:
  • 1.禁用缩放
  
  • 2.使用 fastclick 插件 解决300ms 延迟(最常用)
    // 引入 fastclick.js 文件

    ...
    
  • 3.利用 touch 事件自己封装函数 解决300ms 延迟
  • 原理:手指离开的时间 - 手指触摸的时间 < 150ms,且没有滑动屏幕,就定义为点击,代码如下:
function tap (obj, callback) {
        var isMove = false;
        var startTime = 0; // 记录触摸时候的时间变量
        obj.addEventListener('touchstart', function (e) {
            startTime = Date.now();    // 记录触摸时间});
        obj.addEventListener('touchmove', function (e) {
            isMove = true;    // 判断是否有滑动,有滑动算拖拽,不算点击
        });
        obj.addEventListener('touchend', function (e) {
            if (!isMove && (Date.now() - startTime) < 150) {    // 如果手指触摸和离开时间小于150ms 算点击
                callback && callback(); // 执行回调函数
            }
            isMove = false;    // 取反 重置
            startTime = 0;
        });}
  tap(div, function(){   // 执行代码  });    // 调用  

 

十一.插件和框架

  • JS 插件:是 js 文件,为了解决某个问题而专门存在,功能单一,并且比较小。
  • JS 插件举例:animate.js 动画插件,fastclick 取消延时插件

1.移动端常见插件网站

Swiper : https://www.swiper.com.cn/

superslide: http://www.superslide2.com/

iscroll: https://github.com/cubiq/iscroll

2.移动端视频插件 zy.media.js

  • 插件使用方法:
  1. 确认插件实现的功能,去官网下载插件
  2. 打开 demo 实例文件,复制结构 html,样式 css,js 插件文件
    // 引入模板所需要的 css文件
    // 引入所需要的 js插件