JavaScript(八)—— PC 端网页特效

本篇为 JavaScript 系列笔记第八篇,将陆续更新后续内容。参考:黑马程序员JavaScript核心教程,前端基础教程

系列笔记:

JavaScript(一)—— 初识JavaScript / 注释 / 输入输出语句 / 变量 / 数据类型

JavaScript(二)—— 运算符 / 流程控制 / 数组

JavaScript(三)—— 函数 / 作用域 / 预解析 / 对象

JavaScript(四)—— 内置对象 / 简单数据类型与复杂类型

JavaScript(五)—— Web APIs 简介 / JavaScript 必须掌握的 DOM 操作

JavaScript(六)—— DOM 事件高级

JavaScript(七)—— BOM 浏览器对象模型
 

文章预览
JavaScript(八)—— PC 端网页特效_第1张图片

 

「一」元素偏移量 offset


offset 系列相关属性可以 动态的 得到该元素的位置(偏移)、大小等:

  1. 获得元素距离带有定位父元素的位置
  2. 获得元素自身的大小(宽度、高度)
  3. 返回的数值不带单位

offset 系列常用属性如下:
JavaScript(八)—— PC 端网页特效_第2张图片

  • offsetTopoffsetLeft 属性

JavaScript(八)—— PC 端网页特效_第3张图片

  1. 父亲有定位,则以 父亲 为准,返回 45
  2. 父亲无定位,则以 body 为准,返回 195
  • offsetWidthoffsetHeight 属性
  1. 计算数值时包含 paddingborderwidth
  2. 可以动态获取数值,比如浏览器缩小时,数值相应变化
  • offsetParent 属性

JavaScript(八)—— PC 端网页特效_第4张图片

  1. 返回带有定位的父亲,否则返回 body
  2. 与节点操作中 parentNode 相比,parentNode 返回最近一级的父亲,无论父亲是否有定位
  • offset 与 style 区别

offset

  1. offset 可以得到任意样式表中的样式值
  2. offset 系列获得的数值是没有单位的
  3. offsetWidth 包含 padding、border、width
  4. offsetWidth 等属性是只读属性,只能获取不能赋值
  5. 只想获取元素大小位置,offset 更合适

style

  1. style 只能得到行内样式表中的样式值
  2. style.width 获得的是带有单位的字符串
  3. style.width 获得不包含 padding 和 border 的值
  4. style.width 是可读写属性,可以获取也可以赋值
  5. 要想给元素更改值,需要用 style

JavaScript(八)—— PC 端网页特效_第5张图片

  • 案例:获取鼠标在盒子内的坐标
    JavaScript(八)—— PC 端网页特效_第6张图片
var box = document.querySelector('.box');
box.addEventListener('mousemove', function (e) {
     
     var x = e.pageX - this.offsetLeft;
     var y = e.pageY - this.offsetTop;
     this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
 })
 box.addEventListener('mouseout', function () {
     
     this.innerHTML = '';
 })
  • 案例:模态框拖拽

JavaScript(八)—— PC 端网页特效_第7张图片

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');
// 显示
link.addEventListener('click', function () {
     
    mask.style.display = 'block';
    login.style.display = 'block';
})
// 隐藏
closeBtn.addEventListener('click', function () {
     
    mask.style.display = 'none';
    login.style.display = 'none';
})
// 拖拽
title.addEventListener('mousedown', function (e) {
     
    var x = e.pageX - login.offsetLeft;
    var y = e.pageY - login.offsetTop; 
    document.addEventListener('mousemove', move);
    function move(e) {
     
        login.style.left = e.pageX - x + 'px';
        login.style.top = e.pageY - y + 'px';
    }
    document.addEventListener('mouseup', function () {
     
        document.removeEventListener('mousemove', move);
    })
})
  1. 鼠标按下 mousedown,获取鼠标在盒子中坐标
  2. 鼠标移动 mousemove,求得模态框的 lefttop
  3. 鼠标弹起 mouseup,移除注册事件 removeEventListener
  • 案例:仿京动放大镜

部分 HTML 代码

<div class="preview_img">
    <img src="img/mac_small.jpg" alt="">	<--小图-->
    <div class="mask">div>				<--遮罩-->
    <div class="preview_img_big">			<--大图-->
        <img src="img/mac_big.jpg" alt="" class="big_img">
    div>
div>

部分 CSS 代码

.preview_img {
     
    position: relative;
    height: 450px;
    border: 1px solid #ccc;
}

.mask {
     
    display: none;
    position: absolute;
    left: 0;
    top: 0;
    width: 300px;
    height: 300px;
    background: #FEDE4F;
    opacity: .5;        /* 不透明度 */
    border: 1px solid #ccc;
    cursor: move;       /* 鼠标样式为移动 */
}

.preview_img_big {
     
    display: none;
    position: absolute;
    left: 450px;
    top: -1px;
    width: 540px;
    height: 540px;
    border: 1px solid #ccc;
    overflow: hidden;
}

.big_img {
     
    position: absolute;
    top: 0;
    left: 0;
}

JS 代码

window.addEventListener('load', function () {
     
    var preview_img = document.querySelector('.preview_img');
    var mask = document.querySelector('.mask');
    var big = document.querySelector('.preview_img_big');

    preview_img.addEventListener('mouseover', function () {
     
        mask.style.display = 'block';
        big.style.display = 'block';
    })

    preview_img.addEventListener('mousemove', move);

    function move(e) {
     
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        // 减去遮罩盒子高度一半 
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y - mask.offsetHeight / 2;
        // 最大移动距离(宽高相等)
        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';
        // 获得大图片
        var big_img = document.querySelector('.big_img');
        // 最大移动距离
        var bigMax = big_img.offsetWidth - big.offsetWidth;
        var bigX = maskX * bigMax / maskMax;
        var bigY = maskY * bigMax / maskMax;
        big_img.style.left = -bigX + 'px';
        big_img.style.top = -bigY + 'px';
    }

    preview_img.addEventListener('mouseout', function () {
     
        mask.style.display = 'none';
        big.style.display = 'none';
    })
})

注意:

  1. 若将 js 文件放在 html 上面,注意一定要先加载窗口 window.addEventListener('load', function(){}),否则注册事件绑定为空,出现报错
    在这里插入图片描述
  2. 理解遮罩层和放大图片移动的算法
     

「二」元素可视区 client


通过 client 相关属性可以动态的得到该元素的边框大小、元素大小

JavaScript(八)—— PC 端网页特效_第8张图片

  • client 系列和 offset 系列区别
    JavaScript(八)—— PC 端网页特效_第9张图片
     

「三」元素滚动 scroll


使用 scroll 相关属性可以动态得到该元素的大小、滚动距离等
JavaScript(八)—— PC 端网页特效_第10张图片
JavaScript(八)—— PC 端网页特效_第11张图片

  • onscroll 事件

如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条,当滚动条向下滚动时,会触发 onscroll 事件

  • 案例:仿淘宝固定右侧侧边栏
    JavaScript(八)—— PC 端网页特效_第12张图片

部分 HTML 代码
JavaScript(八)—— PC 端网页特效_第13张图片

JS 代码

var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
 var bannerTop = banner.offsetTop;
 var sliderbarTop = sliderbar.offsetTop - bannerTop;

 var main = document.querySelector('.main');
 var goBack = document.querySelector('.goBack');
 var mainTop = main.offsetTop;

 document.addEventListener('scroll', function (e) {
     
     if (window.pageYOffset >= bannerTop) {
     
         sliderbar.style.position = 'fixed';
         sliderbar.style.top = sliderbarTop + 'px';
     } else {
     
         sliderbar.style.position = 'absolute';
         sliderbar.style.top = '300px';
     }
     if (window.pageYOffset >= mainTop) {
     
         goBack.style.display = 'block';
     } else {
     
         goBack.style.display = 'none';
     }
 })

注意:页面被卷去的头部,有兼容问题,通常有下面几种写法:

  1. 声明了 DTD(),使用 document.documentElement.scrollTop
  2. 未声明 DTD,使用 document.body.scrollTop
  3. IE9 开始,新方法 window.pageYOffsetwindow.pageXOffset

 

「四」动画函数


  • 动画实现原理

通过定时器 setInterval() 不断移动盒子位置。实现步骤:

  1. 获得盒子当前位置
  2. 通过定时器不断重复移动单位距离(需要添加定位,使用 element.style.left )
  3. 添加结束定时条件
    JavaScript(八)—— PC 端网页特效_第14张图片
    JavaScript(八)—— PC 端网页特效_第15张图片
  • 动画函数的封装

可能一个页面中会多次调用动画过程,因此可以将其封装成函数。

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

function animate(obj, target) {
     
    var timer = setInterval(function () {
     
        if (obj.offsetLeft >= target) {
     
            clearInterval(timer);
        }
        obj.style.left = obj.offsetLeft + 2 + 'px';
    }, 10)
}
  • 动画函数给不同元素记录不同定时器

若每次调用都声明 var timer 变量,会造成占用大量内存以及重复命名歧义等问题。因此,这里利用给对象添加属性的方式进行赋值操作 obj.timer,实现了不同元素指定不同定时器
JavaScript(八)—— PC 端网页特效_第16张图片
JavaScript(八)—— PC 端网页特效_第17张图片

  1. 为避免持续点击导致开启多个定时器,应在函数调用开始时清除所有定时器
  2. 为避免停止后点击还会少量移动问题,在到达指定距离后,return 结束函数调用
  • 缓动效果原理

缓动动画就是让元素运动速度有所变化,最常见的是逐渐降速到停止,使得效果更加自然

算法: 步长 = (目标位置 - 当前位置)/ 10

 var step = (target - obj.offsetLeft) / 10;
 step = step > 0 ? Math.ceil(step) : Math.floor(step);

JavaScript(八)—— PC 端网页特效_第18张图片
JavaScript(八)—— PC 端网页特效_第19张图片

  1. 避免小数问题导致最后无法运动到指定 target, 这里利用了向上取整
  2. 此外,考虑到后退过程,向上取整也出现了问题。因此需要分类来讨论
    在这里插入图片描述
  • 动画函数添加回调函数

回调函数原理:函数可以作为一个参数,作为传递到另一个函数中

function animate(obj, target, callback) {
     }	// callback 回调函数

animate(span, 500, function () {
     })

JavaScript(八)—— PC 端网页特效_第20张图片
JavaScript(八)—— PC 端网页特效_第21张图片

  • 动画函数封装到单独 JS 文件中

以后会经常使用动画函数,因此可以将其单独封装到一个 JS 文件中,使用时直接引用 JS 文件即可
在这里插入图片描述

  • 案例:滑动盒子
    JavaScript(八)—— PC 端网页特效_第22张图片
    <div class="sliderbar">
        <span>span>
        <div class="con">问题反馈div>
    div>

    <script>
        var sliderbar = document.querySelector('.sliderbar');
        var con = document.querySelector('.con');
        sliderbar.addEventListener('mouseenter', function () {
       
            animate(con, -160, function () {
       
                sliderbar.children[0].innerHTML = '→';
            });
        })
        sliderbar.addEventListener('mouseleave', function () {
       
            animate(con, 0, function () {
       
                sliderbar.children[0].innerHTML = '←';
            });
        })
    script>
  1. mouseentermouseleavemouseovermouseout 用法一样,区别在于前者无法冒泡
  2. 注意要引用 animate.js
     

「五」常见网页特效案例


  • 案例:淘宝轮播图

JS 代码主要利用
在这里插入图片描述

  1. animate.js 代码
function animate(obj, target, callback) {
     
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
     
        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);
}
  1. index.js 代码
window.addEventListener('load', function () {
     
    var arrow_l = document.querySelector('.arrow-l');
    var arrow_r = document.querySelector('.arrow-r');
    var focus = document.querySelector('.focus');
    var focusWidth = focus.offsetWidth;


    //  鼠标经过 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 () {
     
            //手动调用点击事件
            arrow_r.click();
        }, 2000);
    })


    // 动态生成小圆圈
    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 中又创建了 a,其实在这里不创建 a 也可以,本案例暂且依淘宝为准
        var a = document.createElement('a');
        // 记录索引
        a.setAttribute('index', i);
        ol.appendChild(li);
        li.appendChild(a);
        // 圆圈绑定事件
        li.addEventListener('focus', function () {
     
            this.blur();
        })
 
        a.addEventListener('click', function () {
     
            // 排他思想,被点击的圆圈设置 current 样式
            for (var i = 0; i < ol.children.length; i++) {
     
                ol.children[i].firstChild.className = '';
            }
            this.className = 'current';

            // 移动图片
            var index = this.getAttribute('index');
            animate(ul, - index * focusWidth);

            // 修改索引
            num = circle = index;
        })
    }
    ol.children[0].firstChild.className = 'current';

    // 克隆第一张图片放在 ul 最后,实现无缝滚动(不复制直接添加的话会多出一个小圆圈)
    var first = ul.children[0].cloneNode(true);
    ul.appendChild(first);
    // 点击右侧按钮, 图片滚动一张
    var num = 0;
    // circle 控制小圆圈的播放
    var circle = 0;
    // flag 节流阀, 防止连续点击造成过快播放
    var flag = true;
    // 右侧按钮
    arrow_r.addEventListener('click', function () {
     
        if (flag) {
     
            flag = false; // 关闭节流阀
            // 如果走到最后复制的一张图片,ul 快速复原
            if (num == ul.children.length - 1) {
     
                ul.style.left = 0;
                num = 0;
            }
            num++;
            animate(ul, -num * focusWidth, function () {
     
                flag = true; // 打开节流阀
            });
            circle++;
            // 最后一张时复原
            if (circle == ol.children.length) {
     
                circle = 0;
            }
            // 调用函数
            circleChange();
        }
    })
	// 左侧按钮
    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++) {
     
            ol.children[i].firstChild.className = '';
        }
        ol.children[circle].firstChild.className = 'current';
    }

    // 自动播放轮播图
    var timer = this.setInterval(function () {
     
        arrow_r.click();
    }, 2000);
})
  1. html 代码、css 代码由于篇幅问题不再给出

遇到的问题

  • 开始写轮播图效果时,遇到点击 li 后出现光标持续闪烁的问题,尝试了各种方法,都没有很好的解决
    在这里插入图片描述
    如上图所示输入光标
  • 最后灵机一动,发现其实这个问题只需要给整个盒子设置 font-size: 0;,即可完美解决

 


  • 案例:动画返回页面顶部

在此前案例中,返回顶部使用的是锚点链接,在此处介绍一种新方式:

window.scroll(x, y)   // 滚动窗口至文档中的指定位置

本案例利用 window.scroll(x, y) 配合动画函数 animate 实现动画返回顶部效果
JavaScript(八)—— PC 端网页特效_第23张图片

部分 JS 代码

goBack.addEventListener('click', function () {
     
    // 窗口进行滚动, 对象是 window
    animate(window, 0);
    // window.scroll(0, 0);
});

// 动画函数
function animate(obj, target) {
     
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
     
        var step = (target - window.pageYOffset) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (window.pageYOffset == target) {
     
            clearInterval(obj.timer);
        }
        window.scroll(0, window.pageYOffset + step);
    }, 15);
}

这里注意 animate 函数 将参数改为垂直相关,利用 window.pageYOffset


  • 案例:筋斗云导航
    请添加图片描述
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;
    for (var i = 0; i < lis.length; i++) {
     
        // 鼠标进入,当前 li 位置为目标值
        lis[i].addEventListener('mouseenter', function () {
     
            animate(cloud, this.offsetLeft);
        })
        // 鼠标离开,回到起始位置
        lis[i].addEventListener('mouseleave', function () {
     
            animate(cloud, current);
        })
        // 鼠标点击,当前位置设为目标值
        lis[i].addEventListener('click', function () {
     
            current = this.offsetLeft;
        })
    }
})

你可能感兴趣的