JavaScript基础、DOM以及BOM笔记(三)

PC端网页特效

1.元素偏移量 offset 系列

1.1 offset 概述

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

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

offset 系列常用属性:

offset系列属性作用
element.offsetParent返回作为该元素带有定位的父级元素 如果父级都没有定位则返回body
element.offsetTop返回元素相对带有定位父元素上方的偏移
element.offsetLeft返回元素相对带有定位父元素左边框的偏移
element.offsetWidth返回自身包括padding、边框、内容区的宽度,返回数值不带单位
elelment.offsetHeight返回自身包括padding、边框、内容区的高度,返回数值不带单位
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>offset系列属性</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
        .father {
            position: relative;
            width: 300px;
            height: 300px;
            background-color: pink;
            margin: 200px 100px;
        }

        .son {
            width: 150px;
            height: 150px;
            background-color: purple;
            margin-left: 45px;
        }

        .w {
            /* width: 200px; */
            height: 200px;
            background-color: skyblue;
            margin: 0 auto 200px;
            padding: 10px;
            border: 15px solid red; 
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son"></div>
    </div>
    <div class="w"></div>
    <script>
        // offset  系列
        var father = document.querySelector('.father');
        var son = document.querySelector('.son');
        var w = document.querySelector('.w');
        // 1. 可以得到元素的偏移 位置 返回的不带单位的数值
        console.log(father.offsetTop);
        console.log(father.offsetLeft);
        // 它以带有定位的父元素为准  如果没有父元素或者父元素没有定位 则以 body 为准
        console.log(son.offsetLeft);
        // 2. 可以得到元素的大小 宽度和高度 是包含padding + border + width
        console.log(w.offsetWidth);
        console.log(w.offsetHeight);
        // 3. 返回带有定位的父元素 否则的是body
        console.log(son.offsetParent);
        console.log(son.parentNode); // 返回父元素 是最近一级父元素 不管有没有定位
    </script>
</body>
</html>

1.2 offset与style区别

offset

  • offset可以得到任意样式表中的样式值
  • offset系列获得的数值是没有单位的
  • offsetWidth包含padding+border+width
  • offsetWidth等属性是只读属性,只能获取不能赋值
  • 所以,我们想要获取元素大小位置,有offset更合适

style

  • style只能得到行内样式表中的样式
  • style.width获得的是带有单位的字符串
  • style.width获得不包含padding和border的值
  • style.width是可读写属性,可以获取也可以赋值
  • 所以,我们想要给元素更改值,则需要用style改变
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>offset和style的区别</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: pink;
            padding: 10px;
        }
    </style>
</head>
<body>
    <div class="box" style="width: 200px;"></div>
    <script>
        var box = document.querySelector('.box');
        console.log(box.offsetWidth);
        console.log(box.style.width);
    </script>
</body>
</html>
案例:获取鼠标在盒子内的坐标
案例分析
  1. 我们在盒子内点击,想要得到鼠标距离盒子左右的距离。
  2. 首先得到鼠标在页面中的坐标(e.pageX, e.pageY)
  3. 其次得到盒子在页面中的距离(box.offsetLeft,box.offsetTop)
  4. 用鼠标距离页面的坐标减去盒子在页面中的距离,得到鼠标在盒子内的坐标
  5. 如果想要移动柜一下鼠标,就要获取最新的坐标,使用鼠标移动事件mousemove
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算鼠标在盒子内的坐标</title>
    <style>
        .box {
            width: 300px;
            height: 300px;
            background-color: pink;
            margin: 200px 200px;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        var box = document.querySelector('.box');
        box.addEventListener('mousemove', function(e) {
            // console.log(e.pageX);
            // console.log(e.pageY);
            var x = e.pageX - this.offsetLeft;
            var y = e.pageY - this.offsetTop;
            this.innerHTML = 'x坐标是' + x + '  y坐标是' + y;
        })
    </script>  
</body>
</html>
案例:模态框拖拽

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

  1. 点击弹出层,会弹出模态框,并且显示灰色半透明的遮挡层。
  2. 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮罩层。
  3. 鼠标放到模态层最上面一行,可以按住鼠标拖拽模态框在页面中移动。
  4. 鼠标松开,可以停止拖动模态框移动。
案例分析
  1. 点击弹出层,模态层和遮罩层就会显示出来display:block;
  2. 点击关闭按钮,模态框和遮罩层就会隐藏起来display:none;
  3. 在页面中拖拽的原理:鼠标按下并且移动,之后松开鼠标
  4. 触发事件是鼠标按下mousedown,鼠标移动mousemove鼠标松开mouseup
  5. 拖拽过程:鼠标移动过程中,获得最新的值赋值给模态框的lef和top值,这样模态框就可以跟着鼠标走了
  6. 鼠标按下触发的事件源是最上面一行,就是id为title
  7. 鼠标的坐标减去鼠标在盒子内的坐标,才是模态框真正的位置。
  8. 鼠标按下,我们要得到鼠标在盒子的坐标。
  9. 鼠标移动,就让模态框的坐标设置为:鼠标坐标减去盒子坐标即可,注意移动事件写到按下事件里面。
  10. 鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模态框拖拽</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }

        .login-header {
            width: 100%;
            text-align: center;
            font-size: 24px;
            margin-top: 15px;
        }

        input {
            border: 0;
        }

        a {
            color: #000;
            text-decoration: none;
        }

        .login {
            display: none;
            width: 512px;
            height: 280px;
            position: fixed;
            border: #ebebeb solid 1px;
            left: 50%;
            top: 50%;
            background: #ffffff;
            box-shadow: 0px 0px 20px #ddd;
            z-index: 9999;
            transform: translate(-50%, -50%);
            text-align: center;
        }

        .login-title {
            text-align: center;
            font-size: 20px;
            margin: 15px 0 20px 0;
        }

        .login-title:hover {
            cursor: move;
        }

        .login-title span {
            position: absolute;
            top: -24px;
            right: -24px;
            width: 48px;
            height: 48px;
            line-height: 45px;
            text-align: center;
            font-size: 14px;
            background-color: #fff;
            border-radius: 50%;
            border: 1px solid #ccc;
        }

        .login-input {
            overflow: hidden;
        }

        .list-input {
            float: left;
            width: 350px;
            height: 35px;
            line-height: 35px;
            border: 1px solid #ebebeb;
            margin-bottom: 20px;
            text-indent: 6px; 
        }

        .login-input label {
            float: left;
            width: 90px;
            padding-right: 10px;
            text-align: right;
            line-height: 35px;
            height: 35px;
            font-size: 14px;
        }

        .login-button {
            width: 50%;
            height: 42px;
            line-height: 42px;
            margin: 15px auto 0px;
            font-size: 14px;
            border: 1px solid #ebebeb;
        }

        .login-button a {
            display: block;
        }

        .login-bg {
            display: none;
            width: 100%;
            height: 100%;
            position: fixed;
            top: 0;
            left: 0;
            background: rgba(0, 0, 0, .3);
        }
    </style>
</head>
<body>
    <div class="login-header"><a href="javascript:;" id="link">点击,弹出登录框</a></div>
    <div class="login" id="login">
        <div class="login-title" id="title">登录会员
            <span><a href="javascript:void(0);" id="closeBtn" class="close-login">关闭</a></span>
        </div>
        <div class="login-input-content">
            <div class="login-input">
                <label for="">用户名: </label>
                <input type="text" id="username" class="list-input" placeholder="请输入用户名" name="info[username]"> 
            </div>
            <div class="login-input">
                <label for="">登录密码: </label>
                <input type="password" name="info[password]" id="password" class="list-input" placeholder="请输入登录密码">
            </div>
        </div>
        <div class="login-button" id="loginBtn"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
    </div>
    <!-- 遮盖层 -->
    <div class="login-bg" id="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', 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>
</html>
案例:仿京东放大镜
案例分析
  1. 整个案例可以分为三个功能模块
  2. 鼠标经过小图片盒子,黄色的遮挡层和大图片显示,离开隐藏2个盒子功能
    • 就是显示与隐藏
  3. 黄色的遮挡层跟随鼠标功能
    • 把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。
    • 首先是获得鼠标在盒子的坐标
    • 之后把数值给遮挡层作为left和top值
    • 此时用到鼠标移动事件,但是还是在小图片盒子内移动
    • 发现遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。
    • 遮挡层不能超出小图片盒子范围
    • 如果小于零,就把坐标设置为0
    • 遮挡层最大移动距离:小图片盒子宽度减去遮挡层盒子宽度
  4. 移动黄色遮挡层,大图片跟随移动功能
    • 求大图片的移动距离公式
    • 遮挡层移动距离/遮挡层最大移动距离 = 大图片移动距离/大图片最大移动距离
    • 大图片移动距离 = 遮挡层移动距离*大图片最大移动距离/遮挡层最大移动距离
 <div class="product_intro clearfix">
            <!-- 预览区域 -->
            <div class="preview_wrap fl">
                <div class="preview_img">
                    <img src="upload/s3.png" alt="">
                    <div class="mask"></div>
                    <div class="big">
                        <img src="upload/big.jpg" alt="" class="bigImg">
                    </div>
                </div>

                <div class="preview_list">
                    <a href="#" class="arrow_prev"></a>
                    <a href="#" class="arrow_next"></a>
                    <ul class="list_item">
                        <li>
                            <img src="upload/pre.jpg" alt="">
                        </li>
                        <li class="current">
                            <img src="upload/pre.jpg" alt="">
                        </li>
                        <li>
                            <img src="upload/pre.jpg" alt="">
                        </li>
                        <li>
                            <img src="upload/pre.jpg" alt="">
                        </li>
                        <li>
                            <img src="upload/pre.jpg" alt="">
                        </li>
                    </ul>
                </div>
            </div>
            <!-- 产品详细信息 -->
            <div class="itemInfo_wrap fr">
                <div class="sku_name">
                    Apple iPhone 6s(A1700)64G玫瑰金色 移动通信电信4G手机
                </div>
                <div class="news">
                    推荐选择下方[移动优惠购],手机套餐齐搞定,不用换号,每月还有花费返
                </div>
                <div class="summary">
                    <dl class="summary_price">
                        <dt>价格</dt>
                        <dd>
                            <i class="price">¥5299.00 </i>

                            <a href="#">降价通知</a>

                            <div class="remark">累计评价612188</div>

                        </dd>
                    </dl>
                    <dl class="summary_promotion">
                        <dt>促销</dt>
                        <dd>
                            <em>加购价</em> 满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换 购热销商品 详情 》

                        </dd>
                    </dl>
                    <dl class="summary_support">
                        <dt>支持</dt>
                        <dd>以旧换新,闲置手机回收 4G套餐超值抢 礼品购</dd>
                    </dl>
                    <dl class="choose_color">
                        <dt>选择颜色</dt>
                        <dd>
                            <a href="javascript:;" class="current">玫瑰金</a>
                            <a href="javascript:;">金色</a>
                            <a href="javascript:;">白色</a>
                            <a href="javascript:;">土豪色</a>
                        </dd>
                    </dl>
                    <dl class="choose_version">
                        <dt>选择版本</dt>
                        <dd>
                            <a href="javascript:;" class="current">公开版</a>
                            <a href="javascript:;">移动4G</a>
                        </dd>
                    </dl>
                    <dl class="choose_type">
                        <dt>购买方式</dt>
                        <dd>
                            <a href="javascript:;" class="current">官方标配</a>
                            <a href="javascript:;">移动优惠购</a>
                            <a href="javascript:;">电信优惠购</a>
                        </dd>
                    </dl>
                    <div class="choose_btns">
                        <div class="choose_amount">
                            <input type="text" value="1">
                            <a href="javascript:;" class="add">+</a>
                            <a href="javascript:;" class="reduce">-</a>
                        </div>
                        <a href="#" class="addcar">加入购物车</a>
                    </div>
                </div>
            </div>
        </div>

/*详情页的样式文件*/

.de_container {
    margin-top: 20px;
}

.crumb_wrap {
    height: 25px;
}

.crumb_wrap a {
    margin-right: 10px;
}

.preview_wrap {
    width: 400px;
    height: 590px;
}

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

.mask {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    width: 300px;
    height: 300px;
    background: #FEDE4F;
    opacity: .5;
    border: 1px solid #ccc;
    cursor: move;
}

.big {
    display: none;
    position: absolute;
    left: 410px;
    top: 0;
    width: 500px;
    height: 500px;
    background-color: pink;
    z-index: 999;
    border: 1px solid #ccc;
    overflow: hidden;
}

.big img {
    position: absolute;
    top: 0;
    left: 0;
}

.preview_list {
    position: relative;
    height: 60px;
    margin-top: 60px;
}

.list_item {
    width: 320px;
    height: 60px;
    margin: 0 auto;
}

.list_item li {
    float: left;
    width: 56px;
    height: 56px;
    border: 2px solid transparent;
    margin: 0 2px;
}

.list_item li.current {
    border-color: #c81623;
}

.arrow_prev, .arrow_next {
    position: absolute;
    top: 15px;
    width: 22px;
    height: 32px;
    background-color: purple;
}

.arrow_prev {
    left: 0;
    background: url(../img/arrow-prev.png) no-repeat;
}

.arrow_next {
    right: 0;
    background: url(../img/arrow-next.png) no-repeat;
}

.itemInfo_wrap {
    width: 718px;
}

.sku_name {
    height: 30px;
    font-size: 16px;
    font-weight: 700;
}

.news {
    height: 32px;
    color: #e12228;
}

.summary dl {
    overflow: hidden;
}

.summary dt, .summary dd {
    float: left;
}

.summary dt {
    width: 60px;
    padding-left: 10px;
    line-height: 36px;
}

.summary_price, .summary_promotion {
    position: relative;
    padding: 10px 0;
    background-color: #fee9eb;
}

.price {
    font-size: 24px;
    color: #e12228;
}

.summary_price a {
    color: #c81623;
}

.remark {
    position: absolute;
    right: 10px;
    top: 20px;
}

.summary_promotion {
    padding-top: 0;
}

.summary_promotion dd {
    width: 450px;
    line-height: 36px;
}

.summary_promotion em {
    display: inline-block;
    width: 40px;
    height: 22px;
    background-color: #c81623;
    text-align: center;
    line-height: 22px;
    color: #fff;
}

.summary_support dd {
    line-height: 36px;
}

.choose_color a {
    display: inline-block;
    width: 80px;
    height: 41px;
    background-color: #f7f7f7;
    border: 1px solid #ededed;
    text-align: center;
    line-height: 41px;
}

.summary a.current {
    border-color: #c81623;
}

.choose_version {
    margin: 10px 0;
}

.choose_version a, .choose_type a {
    display: inline-block;
    height: 32px;
    padding: 0 12px;
    background-color: #f7f7f7;
    border: 1px solid #ededed;
    text-align: center;
    line-height: 32px;
}

.choose_btns {
    margin-top: 20px;
}

.choose_amount {
    position: relative;
    float: left;
    width: 50px;
    height: 46px;
    background-color: pink;
}

.choose_amount input {
    width: 33px;
    height: 44px;
    border: 1px solid #ccc;
    text-align: center;
}

.add, .reduce {
    position: absolute;
    right: 0;
    width: 15px;
    height: 22px;
    border: 1px solid #ccc;
    background-color: #f1f1f1;
    text-align: center;
    line-height: 22px;
}

.add {
    top: 0;
}

.reduce {
    bottom: 0;
    /*禁止鼠标样式*/
    cursor: not-allowed;
    /* pointer  小手  move  移动  */
}

.addcar {
    float: left;
    width: 142px;
    height: 46px;
    background-color: #c81623;
    text-align: center;
    line-height: 46px;
    font-size: 18px;
    color: #fff;
    margin-left: 10px;
    font-weight: 700;
}

.product_detail {
    margin-bottom: 50px;
}

.aside {
    width: 208px;
    border: 1px solid #ccc;
}

.tab_list {
    overflow: hidden;
    height: 34px;
}

/*把背景颜色 底边框都给 li*/

.tab_list li {
    float: left;
    background-color: #f1f1f1;
    border-bottom: 1px solid #ccc;
    height: 33px;
    text-align: center;
    line-height: 33px;
}

/*鼠标单击 li 变化样式   背景变白色 去掉下边框 文字变颜色*/

.tab_list .current {
    background-color: #fff;
    border-bottom: 0;
    color: red;
}

.first_tab {
    width: 104px;
}

.second_tab {
    width: 103px;
    border-left: 1px solid #ccc;
}

.tab_con {
    padding: 0 10px;
}

.tab_con li {
    border-bottom: 1px solid #ccc;
}

.tab_con li h5 {
    /*超出的文字省略号显示*/
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-weight: 400;
}

.aside_price {
    font-weight: 700;
    margin: 10px 0;
}

.as_addcar {
    display: block;
    width: 88px;
    height: 26px;
    border: 1px solid #ccc;
    background-color: #f7f7f7;
    margin: 10px auto;
    text-align: center;
    line-height: 26px;
}

.detail {
    width: 978px;
}

.detail_tab_list {
    height: 39px;
    border: 1px solid #ccc;
    background-color: #f1f1f1;
}

.detail_tab_list li {
    float: left;
    height: 39px;
    line-height: 39px;
    padding: 0 20px;
    text-align: center;
    cursor: pointer;
}

.detail_tab_list .current {
    background-color: #c81623;
    color: #fff;
}

.item_info {
    padding: 20px 0 0 20px;
}

.item_info li {
    line-height: 22px;
}

.more {
    float: right;
    font-weight: 700;
    font-family: 'icomoon';
}
window.addEventListener('load', function() {
    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';
    })
    preview_img.addEventListener('mousemove', function(e) {
        // (1). 先计算出鼠标在盒子内的坐标
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        // console.log(x, y);
        // (2) 减去盒子高度300的一半是150 就是我们mask最终的left和top值
        // (3) mask 移动的距离
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y - mask.offsetHeight / 2;
        var moveWidthX = preview_img.offsetWidth - mask.offsetWidth;
        var moveWidthY = preview_img.offsetHeight - mask.offsetHeight;
        // 如果x坐标小于了0,就让他停在0的位置
        if (maskX <= 0) {
            maskX = 0;
        } else if (maskX >= moveWidthX) {
            maskX = moveWidthX;
        }
        if (maskY <= 0) {
            maskY = 0;
        } else if (maskY >= moveWidthY) {
            maskY = moveWidthY;
        }
        mask.style.left = maskX + 'px';
        mask.style.top = maskY + 'px';
        // 大图片移动距离 = 遮挡层移动距离*大图片最大移动距离/遮挡层最大移动距离
        // 大图
        var bigImg = document.querySelector('.bigImg');
        // 大图片最大移动距离
        var bigMax = bigImg.offsetWidth - big.offsetWidth;
        // 大图片移动距离 X Y
        var bigX = maskX * bigMax / moveWidthX;
        var bigY = maskY * bigMax / moveWidthX;
        bigImg.style.left = -bigX + 'px';
        bigImg.style.top = -bigY + 'px';
    })
})

1.3 offsetX,offsetLeft,offsetWidth的区别

offsetX/offsetY

offsetX和offsetY表示(鼠标位置)相对于最近父级元素的坐标(无论父级是否定位)(不管是谁触发)

.big{
      width: 200px;
      height: 200px;
      border: 1px solid red;
      position: absolute;
      top: 100px;
      left: 100px;
}
.box{
     width: 100px;
     height: 100px;
     border: 1px solid green;
     margin-left: 50px;
}
<div class="big">
    <div class="box"></div>
</div>
var box = document.getElementsByClassName('box')[0];
var big = document.getElementsByClassName('big')[0];

big.onclick = function(e){
    e = e || window.event;
    console.log(e.offsetX);//若点击位置在box里面则返回的数据是鼠标相对于box的坐标,若点击位置在big里面则返回的值是鼠标相对于big的坐标,和是谁触发事件的无关。
}
offsetLeft/offsetTop

元素相对于最近定位父级元素的坐标,若在所有的父级上都没有定位,则相对于整个文档

.big{
     width: 200px;
     height: 200px;
     border: 1px solid red;
     /* position: absolute; */
 }
.small{
     width: 100px;
     height: 100px;
     border: 1px solid green;
}
<div class="big">
     <div class="small"></div>
</div>
var small = document.getElementsByClassName('small')[0];
var big = document.getElementsByClassName('big')[0];

console.log(small.offsetLeft); //不加position结果是9,加上position结果是0
offsetWidth/offsetHeight

返回元素的视觉尺寸(width+padding+border)

.big{
     width: 200px;
     height: 200px;
     border: 1px solid red;
     position: absolute;
     top: 100px;
     left: 100px;
}
.box{
     width: 100px;
     height: 100px;
     border: 1px solid green;
     margin-left: 50px;
}
<div class="big">
   <div class="box"></div>
</div>
var box = document.getElementsByClassName('box')[0];
var big = document.getElementsByClassName('big')[0];

console.log(big.offsetWidth);//202
console.log(box.offsetWidth);//102

2.元素可视区 client系列

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

client系列属性作用
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.clientWidth返回自身包括padding、内容区的宽度,不含边框,返回数值不带单位
element.clientHeight返回自身包括padding、内容区的高度,不包边框,返回数值不带单位
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>client系列</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background-color: pink;
            border: 10px solid red;
            padding: 10px;
        }
    </style>
</head>
<body>
    <div></div>
    <script>
        // client 宽度 和 offsetWidth 最大的区别就是 不包含边框
        var div = document.querySelector('div');
        console.log(div.clientWidth);
    </script>
</body>
</html>
案例:淘宝flexible.js源码分析

立即执行函数(function(){})() 或者 (function() {}())

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

下面三种情况都会刷新页面都会触发load事件。

  1. a标签的超链接
  2. F5或者刷新按钮(强制刷新)
  3. 前进后退按钮

但是火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了页面里。

所以此时后退按钮不能刷新页面。

此时可以使用pageshow事件来触发。这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中。pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加。

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

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

    // set 1rem = viewWidth / 10    设置我们html 元素的文字大小
    function setRemUnit() {
        var rem = docEl.clientWidth / 10
        docEl.style.fontSize = rem + 'px'
    }

    setRemUnit()

    // reset rem unit on page resize  当我们页面尺寸大小发生变化的时候,要重新设置下rem 的大小
    window.addEventListener('resize', setRemUnit)
        // pageshow 是我们重新加载页面触发的事件
    window.addEventListener('pageshow', function(e) {
        // e.persisted 返回的是true 就是说如果这个页面是从缓存取过来的页面,也需要从新计算一下rem 的大小
        if (e.persisted) {
            setRemUnit()
        }
    })

    // detect 0.5px supports  有些移动端的浏览器不支持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))
 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>像素比和pageshow事件</title>
    <script src="07-flexible分析.js"></script>
</head>
<body>
    <script>
        console.log(window.devicePixelRatio);
        window.addEventListener('load', function () {
            alert(11);
        })
    </script>
    <a href="http://www.itcast.cn">链接</a>
</body>
</html>

3.元素滚动 scroll系列

3.1 元素scroll系列属性

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

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

3.2 页面被卷去的头部

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>scroll系列</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            background-color: pink;
            border: 10px solid red;
            padding: 10px;
            overflow: auto;
            /* display: -webkit-box;
            -webkit-line-clamp: 10;
            -webkit-box-orient: vertical;
            text-overflow: ellipsis; */

        }
    </style>
</head>
<body>
    <div>
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
    </div>
    <script>
        // scroll系列
        var div = document.querySelector('div');
        console.log(div.scrollHeight);
        console.log(div.clientHeight);
        // scroll滚动事件当滚动条发生变化的时候会触发的事件
        div.addEventListener('scroll', function() {
            console.log(div.scrollTop);
        })
    </script>
</body>
</html>
案例:仿淘宝固定右侧侧边栏
  1. 原先侧边栏是绝对定位
  2. 当页面滚动到一定位置,侧边栏改为固定定位
  3. 页面继续滚动,会让返回顶部显示出来
案例分析
  1. 需要用到页面滚动事件scroll因为是页面滚动,所以事件源是document
  2. 滚动到某个位置,就是判断页面被卷去的上部值
  3. 页面被卷去的头部:可以通过window.pageYOffset 获得 如果是被卷去的左侧 window.pageXOffset
  4. 注意,元素被卷去的头部是element.scrollTop,如果是页面被卷去的头部则是window.pageXOffset

需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:

  1. 声明了DTD使用document.documentElement.scrollTop
  2. 未声明DTD,使用 document.body.scrollTop
  3. 新方法window.pageYOffset 和 window.pageXOffset, IE9开始支持
  function() {
      return {
		left: window.pageXOffset || documentElement.scrollLeft || document.body.scrollLeft || 0,
        top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
      };
  }
// 使用的时候 getScroll().left
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>仿淘宝固定侧边栏</title><style>
        .slider-bar {
            position: absolute;
            left: 50%;
            top: 300px;
            margin-left: 600px;
            width: 45px;
            height: 130px;
            background-color: pink;
        }
        
        .w {
            width: 1200px;
            margin: 10px auto;
        }
        
        .header {
            height: 150px;
            background-color: purple;
        }
        
        .banner {
            height: 250px;
            background-color: skyblue;
        }
        
        .main {
            height: 1000px;
            background-color: yellowgreen;
        }
        
        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="main w">主体部分</div>
    <script>
        // 1. 获取元素
        var sliderbar = document.querySelector('.slider-bar');
        var banner = document.querySelector('.banner');
        // 获取 main 主体元素
        var main = document.querySelector('.main');
        var goBack = document.querySelector('.goBack');
        var mainTop = main.offsetTop;
        // banner.offsetTop 就是被卷去的头部的大小 一定要写到滚动事件的外面
        var bannerTop = banner.offsetTop;
        // 当我们侧边栏固定定位之后应该变化的数值
        var sliderbarTop = sliderbar.offsetTop - bannerTop;
        // 2. 页面滚动事件 scroll
        document.addEventListener('scroll', function() {
            // console.log(11);
            // window.pageYOffset 页面被卷去的头部
            // console.log(window.pageYOffset);
            // 3. 当我们页面被卷去的头部大于等于了301 此时 侧边栏就要改为固定定位
            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';
            }

        })
    </script>
</body>
</html>

三大系列总结

三大系列大小对比作用
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 只会经过自身盒子触发
  • 之所以这样,就是因为mouseenter不会冒泡
  • 跟mouseenter搭配 鼠标离开 mouseleave 同样不会冒泡
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>mouseenter和mouseover的区别</title>
    <style>
        .father {
            width: 400px;
            height: 400px; 
            background-color: pink;
            margin: 0 auto;
        }

        .son {
            width: 200px;
            height: 200px;
            background-color: purple;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son"></div>
    </div>
    <script>
        var father = document.querySelector('.father');
        var son = document.querySelector('.son');
        father.addEventListener('mouseenter', function () {
            console.log(11);
        })
    </script>
</body>
</html>

4.动画函数封装

4.1 动画实现原理

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

实现步骤:

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>动画原理</title>
    <style>
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background: pink;
            transition: all .3;
        }
    </style>
</head>

<body>
    <div></div>
    <script>
        // 1. 获得盒子当前位置
        // 2. 让盒子在当前位置加上1个移动距离
        // 3. 利用定时器不断重复这个操作
        // 4. 加一个结束定时器的条件
        // 5. 注意此元素需要添加定位,才能使用element.style.left
        var div = document.querySelector('div');
        var timer = setInterval(function() {
            if (div.offsetLeft >= 400) {
                // 停止动画 本质是停止定时器
                clearInterval(timer);
            } else {
                div.style.left = div.offsetLeft + 4 + 'px';
            }
        }, 30)
    </script>
</body>

</html>

4.2 动画函数简单封装

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简单动画函数封装</title>
    <style>
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background: pink;
            transition: all .3;
        }

        span {
            position: relative;
            top: 200px;
            display: block;
            width: 150px;
            height: 150px;
            background-color: purple;
        }
    </style>
</head>
<body>
    <div></div>
    <span>夏雨荷</span>
    <script>
        // 简单动画函数封装 obj目标对象   target目标位置
        function animate(obj, target) {
            var timer = setInterval(function() {
            if (obj.offsetLeft >= target) {
                // 停止动画 本质是停止定时器
                clearInterval(timer);
            } else {
                obj.style.left = obj.offsetLeft + 4 + 'px';
            }
        }, 30)
        }
        var div = document.querySelector('div');
        var span = document.querySelector('span');
        animate(div, 500);
        animate(span, 300);
        
    </script>
</body>
</html>

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

如果多个元素都使用这个动画函数,每次都要var声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。

核心原理:利用js是一门动态语言,可以很方便的给当前对象添加属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>给不同对象添加不同定时器</title>
    <style>
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background: pink;
            transition: all .3;
        }

        span {
            position: relative;
            top: 200px;
            display: block;
            width: 150px;
            height: 150px;
            background-color: purple;
        }
    </style>
</head>
<body>
    <button>点击之后夏雨荷才走</button>
    <div></div>
    <span>夏雨荷</span>
    <script>
        // var obj = {};
        // obj.name = 'andy';
        // 简单动画函数封装 obj目标对象   target目标位置
        // 给不同的元素指定了不同的定时器
        function animate(obj, target) {
            // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案就是让我们元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer)
            obj.timer = setInterval(function() {
            if (obj.offsetLeft >= target) {
                // 停止动画 本质是停止定时器
                clearInterval(obj.timer);
            } else {
                obj.style.left = obj.offsetLeft + 4 + 'px';
            }
        }, 30)
        }
        var div = document.querySelector('div');
        var span = document.querySelector('span');
        var btn = document.querySelector('button');
        animate(div, 500);
        btn.addEventListener('click', function() {
            animate(span, 300);
        })
        
    </script>
</body>
</html>

4.4 缓动效果原理

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

思路:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法:(目标值-现在的位置)/ 10 作为每次移动的距离 步长
  3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
  4. 注意步长值需要取整
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>缓动动画原理</title>
    <style>
        div {
            position: absolute;
            width: 100px;
            height: 100px;
            background: pink;
            transition: all .3;
        }

        span {
            position: relative;
            top: 200px;
            display: block;
            width: 150px;
            height: 150px;
            background-color: purple;
        }
    </style>
</head>

<body>
    <button>点击之后夏雨荷才走</button>
    <div></div>
    <span>夏雨荷</span>
    <script>
        // var obj = {};
        // obj.name = 'andy';
        // 简单动画函数封装 obj目标对象   target目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
        // 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
        function animate(obj, target) {
            // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案就是让我们元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer)
            obj.timer = setInterval(function () {
                // 步长值写到定时器里面
                var step = Math.ceil((target - obj.offsetLeft) / 10)
                if (obj.offsetLeft >= target) {
                    // 停止动画 本质是停止定时器
                    clearInterval(obj.timer);
                } else {
                    // 把每次加1的这个步长值改为慢慢变小的步长值  步长公式:(目标值 - 现在的位置)/ 10 
                    obj.style.left = obj.offsetLeft + step + 'px';
                }
            }, 15)
        }
        var div = document.querySelector('div');
        var span = document.querySelector('span');
        var btn = document.querySelector('button');
        animate(div, 800);
        btn.addEventListener('click', function () {
            animate(span, 1000);
        })
        // 匀速动画 就是 盒子当前的位置加上一个固定值 10
        // 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10  
    </script>
</body>

</html>

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

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

当我们点击按钮时候,判断步长是正值还是负值

  1. 如果是正值,则步长往大了取整。
  2. 如果是负值,则步长向小了取整
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>缓动动画原理</title>
        <style>
            span {
                position: absolute;
                top: 200px;
                left: 0;
                display: block;
                width: 150px;
                height: 150px;
                background-color: purple;
            }
        </style>
    </head>

    <body>
        <button class="btn500">点击夏雨荷500</button>
        <button class="btn800">点击夏雨荷800</button>
        <span>夏雨荷</span>
        <script>
            // var obj = {};
            // obj.name = 'andy';
            // 简单动画函数封装 obj目标对象   target目标位置
            // 思路:
            // 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
            // 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
            // 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
            function animate(obj, target) {
                // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
                // 解决方案就是让我们元素只有一个定时器执行
                // 先清除以前的定时器,只保留当前的一个定时器执行
                clearInterval(obj.timer)
                obj.timer = setInterval(function () {
                    // 步长值写到定时器里面
                    // 把步长值改为整数 不要出现小数的问题
                    // var step = Math.ceil((target - obj.offsetLeft) / 10);
                    var step = (target - obj.offsetLeft) / 10;
                    step = step > 0 ? Math.ceil(step) : Math.floor(step);
                    if (obj.offsetLeft == target) {
                        // 停止动画 本质是停止定时器
                        clearInterval(obj.timer);
                    } else {
                        // 把每次加1的这个步长值改为慢慢变小的步长值  步长公式:(目标值 - 现在的位置)/ 10 
                        obj.style.left = obj.offsetLeft + step + 'px';
                    }
                }, 15)
            }
            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);
            })
            // 匀速动画 就是 盒子当前的位置加上一个固定值 10
            // 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10  
        </script>
    </body>

    </html>

4.6 动画函数添加回调函数

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>i缓动动画添加回调函数</title>
    <style>
        span {
            position: absolute;
            top: 200px;
            left: 0;
            display: block;
            width: 150px;
            height: 150px;
            background-color: purple;
            color: #fff;
        }
    </style>
</head>
<body>
    <button class="btn500">点击夏雨荷500</button>
    <button class="btn800">点击夏雨荷800</button>
    <span>夏雨荷</span>
    <script>
        // var obj = {};
        // obj.name = 'andy';
        // 简单动画函数封装 obj目标对象   target目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置)/ 10 作为每次移动的距离 步长
        // 3. 停止的条件是:让当前盒子位置等于目标位置就停止定时器
        function animate(obj, target, callback) {
            // console.log(callback);  callback = function() {} 调用的时候 callback()
            // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案就是让我们元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer)
            obj.timer = setInterval(function () {
                // 步长值写到定时器里面
                // 把步长值改为整数 不要出现小数的问题
                // var step = Math.ceil((target - obj.offsetLeft) / 10);
                var step = (target - obj.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                if (obj.offsetLeft == target) {
                    // 停止动画 本质是停止定时器
                    clearInterval(obj.timer);
                    // 回调函数写到定时器结束里面
                    if (callback) {
                        // 调用函数
                        callback();
                    }
                } else {
                    // 把每次加1的这个步长值改为慢慢变小的步长值  步长公式:(目标值 - 现在的位置)/ 10 
                    obj.style.left = obj.offsetLeft + step + 'px';
                }
            }, 15)
        }
        var span = document.querySelector('span');
        var btn500 = document.querySelector('.btn500');
        var btn800 = document.querySelector('.btn800');
        btn500.addEventListener('click', function () {
            animate(span, 500, function() {
                span.style.backgroundColor = 'red';
            });
        })
        btn800.addEventListener('click', function () {
            animate(span, 800, function () {
                span.style.background = 'pink';
            });
        })
        // 匀速动画 就是 盒子当前的位置加上一个固定值 10
        // 缓动动画就是盒子当前的位置 + 变化的值(目标值 - 现在的位置)/ 10  
    </script>
</body>
</html>

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>引用animate动画函数</title>
    <style>
        .sliderbar {
            position: absolute;
            top: 45%;
            right: 0;
        }

        .sliderbar span {
            position: relative;
            display: block;
            width: 40px;
            height: 40px;
            line-height: 40px;
            background: purple;
            text-align: center;
            color: #fff;
            z-index: 1;
            transition: all .4;
        }

        .con {
            position: absolute;
            top: 0;
            /* left: 0; */
            width: 200px;
            height: 40px;
            line-height: 40px;
            text-align: center;
            background: purple;
            color: #fff;
        }
    </style>
    <script src="03-animate.js"></script>
</head>
<body>
    <div class="sliderbar">
        <span></span>
        <div class="con">问题反馈</div>
    </div>

    <script>
        // 当我们鼠标经过 sliderbar 就会让 con这个盒子滑动到左侧
        // 当我们鼠标离开 sliderbar 就会让 con这个盒子滑动到右侧
        var sliderbar = document.querySelector('.sliderbar');
        var con = document.querySelector('.con');
        sliderbar.addEventListener('mouseenter', function() {
            // animate(obj, target, callback)
            animate(con, -160, function() {
                // 当我们动画执行完毕就把 ← 改为 →
                sliderbar.children[0].innerHTML = '→'
            });
        })
        sliderbar.addEventListener('mouseleave', function() {
            animate(con, 0, function() {
                sliderbar.children[0].innerHTML = '←'
            });
        })

    </script>
</body>
</html>

5.常见网页特效案例

案例:网页轮播图

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

功能需求:

  1. 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。

    1. 因为js较多,我们单独新建js文件夹,再新建js文件,引入页面中。
    2. 此时需要添加load事件
    3. 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
    4. 显示隐藏display按钮。
    window.addEventListener('load', function () {
        // 1. 获取元素
        var arrow_l = document.querySelector('.arrow-l');
        var arrow_r = document.querySelector('.arrow-r');
        var focus = document.querySelector('.focus');
        // 2. 鼠标经过focus模块,就显示隐藏左右箭头
        focus.addEventListener('mouseenter', function () {
            arrow_l.style.display = 'block';
            arrow_r.style.display = 'block';
        })
        focus.addEventListener('mouseleave', function () {
            arrow_l.style.display = 'none';
            arrow_r.style.display = 'none';
        })
    })
    
  2. 点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。

  3. 图片播放的同时,下面小圆圈模块跟谁一起变化。

  4. 点击小圆圈,可以播放相应图片。

    1. 动态生成小圆圈
    2. 核心思路:小圆圈的个数要跟图片张数一致
    3. 所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li的个数)
    4. 利用循环动态生成小圆圈(这个小圆圈要放入ol里面)
    5. 创建节点createElement(‘li’)
    6. 插入节点ol.appendChild(li)
    7. 第一个小圆圈需要添加current类
    // 3.动态生成小圆圈 有几张图片,我就生成几个小圆圈
        var ul = focus.querySelector('ul');
        var ol = focus.querySelector('.circle');
        // ul 移动的距离 小圆圈的索引号 乘以图片的宽度 注意是负值
        var focusWidth = focus.offsetWidth;
        // console.log(ul.children.length);
        for (var i = 0; i < ul.children.length; i++) {
            // 创建一个li
            var li = document.createElement('li');
            // 将创建的li插入到ol里
            // 记录当前小圆圈的索引号 通过自定义属性
            li.setAttribute('index-data', i)
            ol.appendChild(li);
        }
    	// 把ol里面的第一个小li设置类名为 current
        ol.children[0].className = 'current';
    
  5. 鼠标不经过轮播图,轮播图也会自动播放图片。

  6. 鼠标经过,轮播图模块,自动播放停止。

  • 小圆圈的排他思想
  • 点击当前小圆圈,就添加current类
  • 其余的小圆圈就移除这个current类
  • 注意:我们在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了
// 4. 小圆圈的排他思想  我们可以在生成的同时直接绑定事件
        li.addEventListener('mouseover', function () {
            // 干掉所有人  把所有的小li清除 current类名
            for (var i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';
            }
            // 留下我自己  当前的小li添加current类名
            this.className = 'current';
        })
  1. 点击小圆圈滚动图片
  2. 此时用到animate动画函数,将js文件引入(注意,因为index.js依赖animate.js所以,animate.js要写到index.js上面)
  3. 使用动画函数的前提,该元素必须要有定位
  4. 注意是ul移动而不是li
  5. 滚动图片的核心算法:点击某个小圆圈,就让图片滚动小圆圈的索引号乘以图片的宽度作为ul移动的距离
  6. 此时需要知道小圆圈的索引号,我们可以在生成小圆圈的时候,给他设置一个自定义属性,点击的时候获取这个自定义属性即可。
// 5. 点击小圆圈移动图片 当然移动的是ul
            // 当我们点击了某个小li 就拿到当前小li的索引号
            var index_data = this.getAttribute('index-data');
            // 当我们点击了某个小li 就要把这个小li的索引号给 num
            num = index_data;
            // 当我们点击了某个小li 就要把这个小li的索引号给 circle
            circle = index_data;
            // console.log(index_data); 
            // console.log(focusWidth);
            animate(ul, -index_data * focusWidth)
  • 点击右侧按钮一次,就让图片滚动一张。
  • 声明一个变量num,点击一次,自增1,让这个变量乘以图片宽度,就是ul的滚动距离
  • 图片无缝滚动原理
  • 把ul第一个li复制一份,放到ul的最后面
  • 当图片滚动到克隆的最后一张图片时,让ul快速的,不做动画的跳到最左侧:left为0
  • 同时num赋值为0,可以从新开始滚动图片了
// 7. 点击右侧按钮 图片滚动一张
    var num = 0;
    // circle 控制小圆圈的播放
    var circle = 0;
    arrow_r.addEventListener('click', function () {
        // 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
        // console.log(num);
        if (num == ol.children.length) {
            ul.style.left = '0px';
            num = 0;
        }
        num++;
        animate(ul, -num * focusWidth)
  1. 克隆第一张图片
  2. 克隆ul第一个li cloneNode() 加 true 深克隆 复制里面的子节点 false 浅克隆
// 6. 克隆第一张图片(li),放到ul最后面
    var first = ul.children[0].cloneNode(true);
    ul.appendChild(first);
  • 点击右侧按钮,小圆圈跟随变化
  • 最简单的做法是再声明一个变量circle,每次点击自增1,注意左侧按钮也需要这个变量 ,因此要声明全局变量
  • 但是图片有5张,我们小圆圈只有4个少一个,必须加一个判断条件
  • 如果circle == 4 就从新复原为0
// 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
        circle++;
        // 如果我们circle == 4 说明走到最后我们克隆的这张图片了,我们就复原
        if (circle == ul.children.length - 1) {
            circle = 0;
        }
        // 先清除其他小圆圈的current类名
        for (var i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        }
        // 留下当前小圆圈的current类名
        ol.children[circle].className = 'current';
  1. 添加一个定时器
  2. 自动播放轮播图,实际就类似点击了右侧按钮
  3. 此时我们使用手动调用右侧按钮点击事件arrow_r.click()
  4. 鼠标经过focus就停止定时器
  5. 鼠标离开就开启定时器

5.1 节流阀

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

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

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

开始设置一个变量 var flag = true;

if (flag) {flag = false;do something} 关闭水龙头

利用回调函数动画执行完毕,flag = true 打开水龙头

<div class="main">
            <div class="focus fl">
                <!-- 左侧箭头 -->
                <a href="javascript: void(0);" class="arrow-l">&lt;</a>
                <!-- 右侧箭头 -->
                <a href="javascript: void(0);" class="arrow-r">&gt;</a>
                <!-- 核心的滚动区域 -->
                <ul>
                    <li>
                        <a href="#"><img src="upload/focus.png" alt=""></a>
                    </li>
                    <li>
                        <a href="#"><img src="upload/focus1.jpg" alt=""></a>
                    </li>
                    <li>
                        <a href="#"><img src="upload/focus2.jpg" alt=""></a>
                    </li>
                    <li>
                        <a href="#"><img src="upload/focus3.jpg" alt=""></a>
                    </li>
                </ul>
                <!-- 小圆点 -->
                <ol class="circle">
                    
                </ol>
            </div>
.focus {
    overflow: hidden;
    position: relative;
    float: left;
    width: 721px;
    height: 455px;
}

.focus ul {
    position: absolute;
    top: 0;
    left: 0;
    width: 500%;
}

.focus ul li {
    float: left;
}

.arrow-l,
.arrow-r {
    display: none;
    position: absolute;
    top: 50%;
    width: 36px;
    height: 50px;
    line-height: 48px;
    text-align: center;
    font-size: 26px;
    background: rgba(0,0,0, .2);
    color: #fff;
    transform: translate(0, -50%);
    z-index: 2;
}

.focus .arrow-r {
    right: 0;
}

.circle {
    position: absolute;
    bottom: 10px;
    left: 50px;
}

.current {
    background-color: #fff;
}

.circle li {
    float: left;
    width: 15px;
    height: 15px;
    margin: 0 6px;
    border: 2px solid #ccc;
    border-radius: 50%;
    cursor: pointer;
}
function animate(obj, target, callback) {
    // console.log(callback);  callback = function() {} 调用的时候 callback()
    // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
    // 解决方案就是让我们元素只有一个定时器执行
    // 先清除以前的定时器,只保留当前的一个定时器执行
    clearInterval(obj.timer)
    obj.timer = setInterval(function () {
        // 步长值写到定时器里面
        // 把步长值改为整数 不要出现小数的问题
        // var step = Math.ceil((target - obj.offsetLeft) / 10);
        var step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (obj.offsetLeft == target) {
            // 停止动画 本质是停止定时器
            clearInterval(obj.timer);
            // 回调函数写到定时器结束里面
            // if (callback) {
            //     // 调用函数
            //     callback();
            // }
            callback && callback();
        } else {
            // 把每次加1的这个步长值改为慢慢变小的步长值  步长公式:(目标值 - 现在的位置)/ 10 
            obj.style.left = obj.offsetLeft + step + 'px';
        }
    }, 15)
}
window.addEventListener('load', function () {
    // 1. 获取元素
    var arrow_l = document.querySelector('.arrow-l');
    var arrow_r = document.querySelector('.arrow-r');
    var focus = document.querySelector('.focus');
    // 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 () {
            arrow_r.click();
        }, 2000)
    })

    // 3.动态生成小圆圈 有几张图片,我就生成几个小圆圈
    var ul = focus.querySelector('ul');
    var ol = focus.querySelector('.circle');
    // ul 移动的距离 小圆圈的索引号 乘以图片的宽度 注意是负值
    var focusWidth = focus.offsetWidth;
    // console.log(ul.children.length);
    for (var i = 0; i < ul.children.length; i++) {
        // 创建一个li
        var li = document.createElement('li');
        // 将创建的li插入到ol里
        // 记录当前小圆圈的索引号 通过自定义属性
        li.setAttribute('index-data', i)
        ol.appendChild(li);
        // 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
            // 当我们点击了某个小li 就拿到当前小li的索引号
            var index_data = this.getAttribute('index-data');
            // 当我们点击了某个小li 就要把这个小li的索引号给 num
            num = index_data;
            // 当我们点击了某个小li 就要把这个小li的索引号给 circle
            circle = index_data;
            // console.log(index_data); 
            // console.log(focusWidth);
            animate(ul, -index_data * focusWidth)
        })
    }
    // 把ol里面的第一个小li设置类名为 current
    ol.children[0].className = 'current';
    // 6. 克隆第一张图片(li),放到ul最后面
    var first = ul.children[0].cloneNode(true);
    ul.appendChild(first);
    // 7. 点击右侧按钮 图片滚动一张
    var num = 0;
    // circle 控制小圆圈的播放
    var circle = 0;
    // flag 节流阀
    var flag = true;
    arrow_r.addEventListener('click', function () {
        if (flag) {
            flag = false;
            // 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
            // console.log(num);
            if (num == ol.children.length) {
                ul.style.left = '0px';
                num = 0;
            }
            num++;
            animate(ul, -num * focusWidth, function () {
                flag = true;  // 打开节流阀
            })
            // 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
            circle++;
            // 如果我们circle == 4 说明走到最后我们克隆的这张图片了,我们就复原
            // if (circle == ul.children.length - 1) {
            //     circle = 0;
            // }
            circle == ul.children.length - 1 ? circle = 0 : circle;
            circleChange()
        }
    })
    // 9. 左侧按钮
    arrow_l.addEventListener('click', function () {
        if (flag) {
            flag = false;
            // 如果走到了最后一张图片 此时我们的ul要快速复原 left改为0
            // console.log(num);
            if (num == 0) {
                num = ul.children.length - 1;
                ul.style.left = -num * focusWidth + 'px';
            }
            num--;
            animate(ul, -num * focusWidth, function() {
                flag = true;
            })
            // 8. 点击右侧按钮,小圆圈跟随变化 可以再声明一个变量控制小圆圈的播放
            circle--;
            // 如果我们circle < 0 说明是第一张图片 则小圆圈要改为第四个小圆圈(3)
            // if (circle < 0) {
            //     circle = ol.children.length - 1;
            // }
            circle = circle < 0 ? ol.children.length - 1 : circle;
            circleChange()
        }
    })
    function circleChange() {
        // 先清除其他小圆圈的current类名
        for (var i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        }
        // 留下当前小圆圈的current类名
        ol.children[circle].className = 'current';
    }
    // 10. 自动播放轮播图
    var timer = setInterval(function () {
        arrow_r.click();
    }, 2000)
})

案例:返回顶部

滚动窗口至文档中的特定位置。

window.scroll(x, y)

注意里面的x和y不跟单位

  1. 带有动画的返回顶部
  2. 此时我们可以继续使用我们封装的动画函数
  3. 只需要把所有的left相关的值改为跟页面垂直滚动距离相关就可以了
  4. 页面滚动了多少,可以通过window.pageYOffset得到
  5. 最后是页面滚动,使用window.scroll(x, y)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>返回顶部案例</title>
    <style>
        .slider-bar {
            position: absolute;
            left: 50%;
            top: 300px;
            margin-left: 600px;
            width: 45px;
            height: 130px;
            background-color: pink;
        }

        .w {
            width: 1200px;
            margin: 10px auto;
        }

        .header {
            height: 150px;
            background-color: purple;
        }

        .banner {
            height: 250px;
            background-color: skyblue;
        }

        .main {
            height: 1000px;
            background-color: yellowgreen;
        }

        span {
            display: none;
            position: absolute;
            bottom: 0;
        }

        .goBack {
            cursor: pointer;
        }
    </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="main w">主体部分</div>
    <script>
        // 1. 获取元素
        var sliderbar = document.querySelector('.slider-bar');
        var banner = document.querySelector('.banner');
        // 获取 main 主体元素
        var main = document.querySelector('.main');
        var goBack = document.querySelector('.goBack');
        var mainTop = main.offsetTop;
        // banner.offsetTop 就是被卷去的头部的大小 一定要写到滚动事件的外面
        var bannerTop = banner.offsetTop;
        // 当我们侧边栏固定定位之后应该变化的数值
        var sliderbarTop = sliderbar.offsetTop - bannerTop;
        // 2. 页面滚动事件 scroll
        document.addEventListener('scroll', function () {
            // console.log(11);
            // window.pageYOffset 页面被卷去的头部
            // console.log(window.pageYOffset);
            // 3. 当我们页面被卷去的头部大于等于了301 此时 侧边栏就要改为固定定位
            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';
            }

        })
        // 3. 当我们点击了返回顶部模块,就让窗口滚动到页面的最上方
        goBack.addEventListener('click', function () {
            // 里面的 x 和 y 不跟单位 直接写数字即可 
            // window.scroll(0, 0);
            animate(window, 0);
        });
        // 动画函数
        function animate(obj, target, callback) {
            // console.log(callback);  callback = function() {} 调用的时候 callback()
            // 当我们不断地点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案就是让我们元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer)
            obj.timer = setInterval(function () {
                // 步长值写到定时器里面
                // 把步长值改为整数 不要出现小数的问题
                // var step = Math.ceil((target - obj.offsetLeft) / 10);
                var step = (target - window.pageYOffset) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                if (window.pageYOffset == target) {
                    // 停止动画 本质是停止定时器
                    clearInterval(obj.timer);
                    // 回调函数写到定时器结束里面
                    callback && callback();
                } else {
                    // 把每次加1的这个步长值改为慢慢变小的步长值  步长公式:(目标值 - 现在的位置)/ 10 
                    // obj.style.left = window.pageYOffset + step + 'px';
                    window.scroll(0, window.pageYOffset + step)
                }
            }, 15)
        }
    </script>
</body>

</html>
案例:筋斗云案例
  • 鼠标经过某个小li,筋斗云跟着到当前小li位置
  • 鼠标离开这个小li,筋斗云复原为原来的位置
  • 鼠标点击了某个小li,筋斗云就会留在点击这个小li的位置
案例分析
  1. 利用动画函数做动画效果
  2. 原先筋斗云的起始位置是0
  3. 鼠标经过某个小li,把当前的offsetLeft位置作为目标值即可
  4. 鼠标离开某个小li,就把目标值设为0
  5. 如果点击了某个小li,就把当前的位置存储起来,当鼠标离开,筋斗云就回到这个位置
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>筋斗云案例</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background: #000;
        }
        
        a {
            color: #000;
            text-decoration: none;
            text-align: center;
        }

        li {
            list-style: none;
        }

        .c-nav  {
            position: relative;
            margin: 100px auto;
            width: 1000px;
            height: 42px;
            background: #fff url(images/rss.png) no-repeat right center;
            border-radius: 6px;
        }

        .c-nav ul {
            position: absolute;
        }

        .c-nav ul li {
            float: left;
            margin: 10px 11px;
        }

        .cloud {
            position: absolute;
            top: 0;
            left: 0;
            display: block;
            width: 83px;
            height: 42px;
            background: url(images/cloud.gif) no-repeat;
        }

        .c-nav li a:hover {
            color: white;
        }

        .c-nav li.current a {
            color: #0dff1d;
        }
    </style>

    <script src="03-animate.js"></script>
    <script>
        window.addEventListener('load', function() {
            // 1. 获取元素
            var cloud = document.querySelector('.cloud');
            var c_nav = document.querySelector('.c-nav');
            var lis = c_nav.querySelectorAll('li');
            // 2. 给所有的小li绑定事件
            var current = 0;
            // 这个current作为筋斗云的起始位置
            for (var i = 0; i < lis.length; i++) {
                // (1) 鼠标经过把当前的小li的位置作为目标值
                lis[i].addEventListener('mouseenter', function() {
                    animate(cloud, this.offsetLeft - 10);
                })
                // (2) 鼠标离开就回到起始的位置
                lis[i].addEventListener('mouseleave', function() {
                    animate(cloud, current);
                })
                lis[i].addEventListener('click', function() {
                    current = this.offsetLeft - 10;
                })
            }
        })
    </script>
</head>
<body>
    <div class="c-nav" id="c_nav">
        <span class="cloud"></span>
        <ul>
            <li class="current"><a href="#">首页新闻</a></li>
            <li><a href="#">师资力量</a></li>
            <li><a href="#">活动策划</a></li>
            <li><a href="#">企业文化</a></li>
            <li><a href="#">招聘信息</a></li>
            <li><a href="#">公司简介</a></li>
            <li><a href="#">你是佩奇</a></li>
            <li><a href="#">啥是佩奇</a></li>
        </ul>
    </div>
</body>
</html>

移动端网页特效

1. 触屏事件

1.1 触屏事件概述

移动端浏览器兼容性好,我们不需要考虑以前js的兼容性问题,可以放心的使用原生js书写效果,但是移动端也有自己独特的地方。比如触屏事件touch(也称触摸事件),Android和iOS都有。

常见的触屏事件如下:

触屏touch事件说明
touchstart手指触摸到一个dom元素时触发
touchmove手指在一个dom元素上滑动时触发
touchend手指从一个dom元素上移开时触发
	<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            background-color: pink;
        }
    </style>
</head>
<body>
    <div></div>
</body>
</html>

<script>
    var div = document.querySelector('div');
    // 1. 手指触摸DOM元素事件
    div.addEventListener('touchstart', function() {
        console.log('我摸了你');
    })
    // 2. 手指在DOM元素身上移动事件
    div.addEventListener('touchmove', function() {
        console.log('我继续摸');
    })

    // 3. 手指离开DOM元素事件
    div.addEventListener('touchend', function() {
        console.log('轻轻的我走了');
    })
</script>

1.2 触摸事件对象(TouchEvent)

TouchEvent是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。这类事件用于描述一个或多个触电,使开发者可以检测触点的移动,触点的增加和减少,等等

touchstart、touchmove、touchend三个事件都会各自有事件对象。

触摸事件对象重点我们看三个常见对象列表:

触摸列表说明
touches正在触摸屏幕的所有手指的一个列表
targetTouches正在触摸当前DOM元素上的手指的一个列表
changeTouches手指状态发生了改变的列表,从无到有,从有道无的变化
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        div {
            width: 100px;
            height: 100px;
            background-color: pink;
        }
    </style>
</head>
<body>
    <div></div>
</body>
</html>

<script>
    var div = document.querySelector('div');
    // 1. 手指触摸DOM元素事件
    div.addEventListener('touchstart', function(e) {
        // console.log(e);
        // touches 正在触摸屏幕的所有手指的列表
        // targetTouches 正在触摸当前DOM元素的手指列表
        // changeTouches 手指状态发生了改变的列表,从无到有或者从有到无
        // 因为我们一般都是触摸元素,所以最经常使用的是 targetTouches
        console.log(e.targetTouches[0]);
        // targetTouches[0] 就可以得到正在触摸dom元素的第一个手指的相关信息比如手指的坐标等等
    })
    // 2. 手指在DOM元素身上移动事件
    div.addEventListener('touchmove', function() {
        
    })

    // 3. 手指离开DOM元素事件
    div.addEventListener('touchend', function() {
        console.log('轻轻的我走了');
        // 当我们手指离开屏幕的时候,就没有了touches和targetTouches列表,但是会有changeTouches
    })
</script>

1.3 移动端拖动元素

  1. touchstart、touchmove、touchend可以实现拖动元素
  2. 但是拖动元素需要当前手指的坐标值我们可以使用targetTouches[0]里面的pageX和pageY
  3. 移动端拖动的原理:手指移动中,计算出手指移动的距离。然后用盒子原来的位置+手指移动的距离
  4. 手指移动的距离:手指滑动中的位置减去 手指刚开始触摸的位置

拖动元素三部曲:

  • 触摸元素touchstart:获取手指初始坐标,同时获得盒子原来的位置
  • 移动手指touchmove:计算手指滑动的距离,并且移动盒子
  • 离开手指touchend;

注意:手指移动也会触发滚动屏幕所以这里要阻止默认的屏幕滚动e.preventDefault();

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible"
        content="IE=edge">
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            position: absolute;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
            background-color: pink;
        }
    </style>
</head>

<body>
    <div></div>
</body>

</html>

<script>
    // 触摸元素touchstart:获取手指初始坐标,同时获得盒子原来的位置
    // 移动手指touchmove:计算手指滑动的距离,并且移动盒子
    // 离开手指touchend;
    var div = document.querySelector('div');
    var startX = 0;
    var startY = 0;  // 手指初始坐标
    var x = 0;
    var y = 0;  // 盒子原来的位置
    div.addEventListener('touchstart', function (e) {
        // 获取手指初始坐标
        startX = e.targetTouches[0].pageX;
        startY = e.targetTouches[0].pageY;
        x = this.offsetLeft;
        y = this.offsetTop;
    })

    div.addEventListener('touchmove', function (e) {
        // 阻止屏幕滚动的默认行为
        e.preventDefault();
        // 计算手指的移动距离: 手指移动之后的坐标减去手指初始的坐标
        var moveX = e.targetTouches[0].pageX - startX;
        var moveY = e.targetTouches[0].pageY - startY;
        // 移动盒子  盒子原来的位置 + 手指移动的距离
        this.style.left = x + moveX + 'px';
        this.style.top = y + moveY + 'px';

    })
</script>
案例:移动端轮播图

移动端轮播图功能和pc端基本一致。

  1. 可以自动播放图片
  2. 手指可以拖动播放轮播图
案例分析

自动播放功能

  1. 开启定时器

  2. 移动端移动,可以使用translate移动

自动播放功能-无缝滚动

  1. 注意,我们判断条件是要等到图片滚动完毕再去判断,就是过渡完成后判断
  2. 此时需要添加检测过渡完成事件 transitionend
  3. 判断条件:如果索引号等于3说明走到最后一张图片,此时索引号要复原为0
  4. 此时图片,去掉过渡效果,然后移动
classList属性

classList属性是HTML5新增的一个属性,返回元素的类名。但是ie10以上版本支持。

该元素用于在元素中添加,移除以及切换css类。有以下方法

添加类:

element.classList.add(‘类名’);

	focus.classList.add('current');

移除类:

element.classList.remove(‘类名’);

	focus.classList.remove('current');

切换类:

element.classList.toggle(‘类名’);

	focus.classList.toggle('current');

小圆点跟随变化效果

  1. 把ol里面li带有current类名的选出来去掉类名 remove
  2. 让当前索引号的小li加上current add
  3. 但是,是等着过渡结束之后变化,所以这个写到transitionend事件里面

手指滑动轮播图

本质就是ul跟谁手指移动,简单来说就是移动端拖动元素

  1. 触摸元素touchstart: 获取手指初始坐标
  2. 移动手指touchmove:计算手指的滑动距离,并且移动盒子
  3. 手指离开touchend:根据滑动的距离分不同的情况
  4. 如果移动距离小于某个像素就回弹原来位置
  5. 如果移动距离大于某个像素就上一张下一张滑动。
window.addEventListener('load', function () {
    // 1. 获取元素
    var focus = document.querySelector('.focus');
    var ul = focus.children[0];
    var ol = focus.children[1];
    // 获取focus的宽度
    var w = focus.offsetWidth;
    // 2. 利用定时器自定轮播图片
    var index = 0;
    var flag = false;
    var timer = setInterval(function () {
        index++;
        var translateX = -index * w;
        ul.style.transition = 'all .4s';
        ul.style.transform = 'translate(' + translateX + 'px)';
    }, 2000)
    // 等着我们过渡完之后,再去判断监听过渡完成的事件 transitionend
    ul.addEventListener('transitionend', function () {
        // 无缝滚动
        if (index >= ul.children.length - 2) {
            index = 0;
            // 去掉过渡效果这样让我们的ul快速的跳到目标位置
            ul.style.transition = 'none';
            // 利用最新索引号乘以宽度,去滚动图片
            var translateX = -index * w;
            ul.style.transform = 'translate(' + translateX + 'px)';
        } else if (index < 0) {
            index = ul.children.length - 3;
            ul.style.transition = 'none';
            // 利用最新索引号乘以宽度,去滚动图片
            var translateX = -index * w;
            ul.style.transform = 'translate(' + translateX + 'px)';
        }
        // (3) 小圆点跟随变化
        // 1. 把ol里面li带有current类名的选出来去掉类名 remove
        ol.querySelector('li.current').classList.remove('current');
        // 2. 让当前索引号的小li加上current add
        ol.children[index].classList.add('current');
    })

    // (4) 手指滑动轮播图
    // 1. 触摸元素touchstart: 获取手指初始坐标
    var startX = 0;
    var moveX = 0;
    ul.addEventListener('touchstart', function (e) {
        startX = e.targetTouches[0].pageX;
        // 手指触摸的时候停止定时器
        clearInterval(timer);
    })
    // 2. 移动手指touchmove:计算手指的滑动距离,并且移动盒子
    ul.addEventListener('touchmove', function (e) {
        e.preventDefault();  // 阻止屏幕滚动的行为
        // 计算移动距离
        moveX = e.targetTouches[0].pageX - startX;
        if (index >= ul.children.length - 2) {
            index = 0;
            // 去掉过渡效果这样让我们的ul快速的跳到目标位置
            ul.style.transition = 'none';
            // 利用最新索引号乘以宽度,去滚动图片
            var translateX = -index * w;
            ul.style.transform = 'translate(' + translateX + 'px)';
        } else if (index < 0) {
            index = ul.children.length - 3;
            ul.style.transition = 'none';
            // 利用最新索引号乘以宽度,去滚动图片
            var translateX = -index * w;
            ul.style.transform = 'translate(' + translateX + 'px)';
        }
        //  移动盒子: 盒子原来的位置 + 手指移动的距离
        var translateX = -index * w + moveX;
        // 手指拖动的时候,不需要动画效果所以要取消过渡效果
        ul.style.transition = 'none';
        ul.style.transform = 'translate(' + translateX + 'px)';
        flag = true; // 如果用户手指移动过,再去判断否则不做判断效果
    })
    // 手指离开touchend:根据滑动的距离分不同的情况 
    ul.addEventListener('touchend', function () {
        // (1) 如果移动距离大于50像素我们就播放上一张或者下一张
        if (flag) {
            if (Math.abs(moveX) > 50) {
                // 如果是右划就是播放上一张moveX是正值
                // if (moveX > 0) {
                //     index--;
                // } else {
                //     // 如果是左划就是播放上一张moveX是负值
                //     index++;
                // }

                moveX > 0 ? index-- : index++;
                var translateX = -index * w;
                ul.style.transition = 'all .4s';
                ul.style.transform = 'translate(' + translateX + 'px)';

            } else {
                // (2) 如果移动距离小于50像素我们就回弹
                var translateX = -index * w;
                ul.style.transition = 'all .1s';
                ul.style.transform = 'translate(' + translateX + 'px)';
            }
            flag = false;
        }
        // 手指离开重启定时器
        clearInterval(timer);
        timer = setInterval(function () {
            index++;
            var translateX = -index * w;
            ul.style.transition = 'all .4s';
            ul.style.transform = 'translate(' + translateX + 'px)';
        }, 2000)
    })
})	
案例:返回顶部

当页面滚动到某个地方,就显示,否则隐藏

点击可以返回顶部

案例分析
  1. 滚动某个地方显示
  2. 事件:scroll页面滚动事件
  3. 如果被卷去的头部(window.scrollY)大于某个数值
  4. 点击,window.scroll(0, 0) 返回顶部

2. 移动端常见特效

2.1 click延时解决方案

移动端click事件会有300ms的延时,原因是移动端屏幕双击会缩放(double tap to zoom)页面。

解决方案:

  1. 禁用缩放。浏览器禁用默认的双击缩放行为并且去掉300ms的点击延迟。
	<meta name="viewport" content="user-scalable=no">
  1. 利用touch事件自己封装这个事件解决300ms延迟。

原理就是:

  1. 当我们手指触摸屏幕,记录当前触摸时间
  2. 当我们手指离开屏幕,用离开的时间减去触摸的时间
  3. 如果时间小于150ms,并且没有滑动屏幕,那么我们就定位为点击
// 封装tap,解决click 300ms 延时
    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() {
        // 执行代码
    })
  1. 使用插件。fastclick插件解决300ms延迟。

3. 移动端常用开发插件

3.1 什么是插件

移动端要求的是快速开发,所以我们经常会借助于一些插件来帮我们完成操作,那么什么是插件呢?

js插件就是js文件,它遵循一定规范编写,方便程序展示效果,拥有特定功能且方便调用。如轮播图和瀑布流插件。

特点:它一般是为了解决某个问题而专门存在,其功能单一,并且比较小。

我们以前写的animate.js也算一个最简单的插件

fastclick插件解决300ms延迟。使用延时

GitHub官网地址:https://github.com/ftlabs/fastclick

3.2 插件的使用

  1. 引入js文件
  2. 按照相关插件规定语法使用

3.3 Swiper插件的使用

中文官网地址:https://www.swiper.com.cn/

  1. 引入插件相关文件
  2. 按照规定语法使用

3.4 其他移动端常见插件

  • superslide:http://www.superslide2.com/
  • iscroll:https://github.com/cubiq/iscroll

3.5 插件的使用总结

  1. 确认插件实现的功能
  2. 去官网查看使用说明
  3. 下载插件
  4. 打开demo实例文件,查看需要引入的相关文件,并且引入
  5. 复制demo实例文件中的结构html,样式css以及js代码

3.6 练习-移动端视频插件 zy.mdeia.js

H5给我们提供了video标签,但是浏览器的支持情况不同。

不同的.视频格式文件,我们可以通过source解决。

但是外观样式,还有暂停,播放,全屏等功能我们只能自己写代码解决。

这个时候我们可以使用插件方式来制作。

4. 移动端常用开发框架

4.1 框架概述

框架,顾名思义就是一套架构,它会基于自身的特点向用户提供一套较为完整的解决方案。框架的控制权在框架本身,使用者要按照框架所规定的某种规范进行开发。

插件一般是为了解决某个问题而专门存在,其功能单一,并且比较小。

前端常用的框架有Bootstart、Vue、Angular、React等。既能开发pc端,也能开发移动端

前端常用的移动端插件有swiper、superslide、iscroll等。

框架:大而全,一整套解决方案

插件:小而专一,某个功能的解决方案

本地存储

1. 本地存储

随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案

本地存储特性
  1. 数据存储在用户浏览器中
  2. 设置、读取方便、甚至页面刷新不丢失数据
  3. 容量较大,sessionStorage约5M、localStorage约20M
  4. 只能存储字符串,可以将对象JSON.stringify()编码后存储

fastclick插件解决300ms延迟。使用延时

GitHub官网地址:

2.window.sessionStorage

  1. 生命周期为关闭浏览器窗口
  2. 在同一个窗口(页面)下数据可以共享
  3. 以键值对的形式存储使用
存储数据:
 sessionStorage.setItem(key, value)
获取数据:
 sessionStorage.getItem(key)
删除数据:
 sessionStorage.removeItem(key)
删除所有数据:
sessionStorage.clear()
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>本地存储之sessionStorage</title>
</head>

<body>
    <input type="text" autofocus="autofocus">
    <button class="set">存储数据</button>
    <button class="get">获取数据</button>
    <button class="remove">删除数据</button>
    <button class="del">清空所有数据</button>
</body>

</html>

<script>
    var ipt = document.querySelector('input');
    var set = document.querySelector('.set')
    var get = document.querySelector('.get')
    var remove = document.querySelector('.remove')
    var del = document.querySelector('.del')
    var flag = 0;
    var num = 0;
    set.addEventListener('click', function () {
        // 当我们点击了之后, 就可以把表单里面的数据存储起来
        // console.log(11);
        var val = ipt.value
        if (ipt.value == '') {
            alert('请输入内容啦,小伙计')
        } else {
            if (flag === 0) {
                sessionStorage.setItem('username', val)
                flag = 1;
                ipt.value = ''
            } else if (flag === 1) {
                sessionStorage.setItem('pwd', val)
                flag = 2;
                ipt.value = ''
            } else if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
                flag = 0
            } else {
                alert('超出存储数量了啦!')
            }
        }
    })
    get.addEventListener('click', function () {
        console.log(sessionStorage.getItem('username'));
    })
    remove.addEventListener('click', function () {
        if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
            alert('没有数据可删了啦, 不要再点啦!')
        } else if (num === 0) {
            sessionStorage.removeItem('username')
            num = 1;
            ipt.value = ''
        } else {
            sessionStorage.removeItem('pwd')
            num = 0;
            ipt.value = ''
        }

    })
    del.addEventListener('click', function () {
        if (sessionStorage.getItem('username') == null && sessionStorage.getItem('pwd') == null) {
            alert('没有数据可删了啦, 不要再点啦!')
        } else {
            sessionStorage.clear()
            ipt.value = ''
        }
    })

    document.addEventListener('keyup', function (e) {
        if (e.keyCode === 13) {
            set.click()
        }
    })
</script>

3.window.localStorage

  1. 生命周期永久生效,除非手动删除否则关闭页面也会存在
  2. 可以多窗口(页面)共享(同一个浏览器可以共享)
  3. 以键值对的形式存储使用
存储数据:
 localStorage.setItem(key, value)
获取数据:
 localStorage.getItem(key)
删除数据:
 localStorage.removeItem(key)
删除所有数据:
localStorage.clear()
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>本地存储之localStorage</title>
</head>

<body>
    <input type="text" autofocus="autofocus">
    <button class="set">存储数据</button>
    <button class="get">获取数据</button>
    <button class="remove">删除数据</button>
    <button class="del">清空所有数据</button>
</body>

</html>

<script>
    var ipt = document.querySelector('input');
    var set = document.querySelector('.set')
    var get = document.querySelector('.get')
    var remove = document.querySelector('.remove')
    var del = document.querySelector('.del')
    var flag = 0;
    var num = 0;
    set.addEventListener('click', function () {
        // 当我们点击了之后, 就可以把表单里面的数据存储起来
        // console.log(11);
        var val = ipt.value
        if (ipt.value == '') {
            alert('请输入内容啦,小伙计')
        } else {
            if (flag === 0) {
                localStorage.setItem('username', val)
                flag = 1;
                ipt.value = ''
            } else if (flag === 1) {
                localStorage.setItem('pwd', val)
                flag = 2;
                ipt.value = ''
            } else if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
                flag = 0;
            } else {
                alert('超出存储数量了啦!')
            }
        }
    })
    get.addEventListener('click', function () {
        console.log(localStorage.getItem('username'));
    })
    remove.addEventListener('click', function () {
        if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
            alert('没有数据可删了啦, 不要再点啦!')
        } else if (num === 0) {
            localStorage.removeItem('username')
            num = 1;
            ipt.value = ''
        } else {
            localStorage.removeItem('pwd')
            num = 0;
            ipt.value = ''
        }

    })
    del.addEventListener('click', function () {
        if (localStorage.getItem('username') == null && localStorage.getItem('pwd') == null) {
            alert('没有数据可删了啦, 不要再点啦!')
        } else {
            localStorage.clear()
            ipt.value = ''
        }
    })

    document.addEventListener('keyup', function (e) {
        if (e.keyCode === 13) {
            set.click()
        }
    })
</script>
案例:记住用户名

如果勾选记住用户名,下次用户打开浏览器,就在文本框里面自动显示上次登录的用户名

案例分析
  1. 把数据存起来,用到本地存储
  2. 关闭页面,也可以显示用户名,所以用到localStorage
  3. 打开页面,先判断是否有这个用户名,如果有,就在表单里面显示用户名,并且勾选复选框
  4. 当复选框发生改变的时候change事件
  5. 如果勾选,就存储,否则就移除
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>记住用户名</title>
</head>
<body>
    <input type="text" id="username">
    <label for="remember"><input type="checkbox" name="checkbox" id="remember">记住用户名</label>
</body>
</html>

<script>
    var username = document.querySelector('#username')
    var remember = document.querySelector('#remember')
    if (localStorage.getItem('username')) {
        username.value = localStorage.getItem('username')
        remember.checked = true;
    }
    remember.addEventListener('change', function() {
        if (this.checked) {
            localStorage.setItem('username', username.value)
        } else {
            localStorage.removeItem('username')
        }
    })
</script>

案例:toDoList

  1. 刷新页面不会丢失数据,因此需要用到本地存储localStorage
  2. 核心思路:不管按下回车,还是点击复选框,都是把本地存储的数据加载到页面中,这样保证刷新页面不会丢失数据
  3. 存储的数据格式:var todolist = [{title: ‘xxx’, done: false}]
  4. 注意点1:本地存储localStorage里面只能存储字符串格式,因此需要把对象转换为字符串JSON.stringify(data)
  5. 注意点2:获取本地存储数据,需要把里面的字符转换为对象格式JSON.parse()我们才能使用里面的数据

toDoList按下回车把新数据添加到本地存储里面

  1. 切记:页面中的数据,都要从本地存储里面获取,这样刷新页面不会丢失数据,所以先要把数据保存到本地存储里面
  2. 利用事件对象.keyCode判断用户按下回车键(13)
  3. 声明一个数组,保存数据
  4. 先要把读取本地存储原来的数据(声明函数getData()),放到这个数组里面
  5. 之后把最新从表单获取过来的数据,追加到数组里面
  6. 最后把数组存储给本地存储(声明函数setDate())

toDoList本地存储数据渲染加载到页面

  1. 因为后面也会经常渲染加载操作,所以声明一个函数load,方便后面调用
  2. 先要读取本地存储数据。(数据不要忘记转换为对象格式)
  3. 之后遍历这个数据,有几条数据,就生成几个小li添加到ol里面
  4. 每次渲染之前,先把原先里面ol的内容清空,然后渲染加载最新的数据

toDoList删除操作

  1. 点击里面的a链接,不是删除的li,而是删除本地存储对应的数据
  2. 核心原理:先获取本地存储数据,删除对应的数据,保存给本地存储,重新渲染列表li
  3. 我们可以给链接自定义属性记录当前的索引号
  4. 根据这个索引号删除相关的数据----数组的splice(i, 1)方法
  5. 存储修改后的数据,然后存储给本地存储
  6. 重新渲染加载数据列表
  7. 因为a是动态创建的,我们要写在创建a的函数内

toDoList正在进行和已完成选项操作

  1. 当我们点击了小的复选框,修改本地存储数据,再重新渲染数据列表
  2. 点击之后,获取本地存储数据
  3. 修改对应数据属性done为当前复选框的checked状态
  4. 之后保存数据到本地存储
  5. 重新渲染加载数据列表
  6. load加载函数里面,新增一个条件,如果当前数据的done为true就是已经完成的,就把列表渲染加载到ul里面
  7. 如果当前数据的done为false,则是待办事项,就把列表渲染加载到ol里面

toDoList统计正在进行个数和已经完成个数

  1. 在我们load函数里面操作
  2. 声明2个变量:todoCount待办个数 doneCount已完成个数
  3. 当进行遍历本地存储数据的时候,如果数据done为false,则todoCount++,否则doneCount++
  4. 最后修改相应的元素innerHTML

toDolist修改本地存储数据

  1. 当我们点击了li,就让当前小li中的p隐藏,插入input标签在p后面并把p元素内的元素给input的value
  2. 循环当前小li中所有的input标签判断个数,小于2的话就插入input防止重复插入
  3. 利用focus()方法自动获取input焦点,selectionEnd = 文本框内容的长度,将文本框内光标定位于最后
  4. 将修改之后的内容给本地存储对应的数据并保存到本地存储
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ToDoList—最简单的待办事项列表</title>
    <meta name="description" content="ToDoList无须注册即可使用,数据存储在用户浏览器的html5本地数据库里,是最简单最安全的待办事项列表应用!">
    <link rel="stylesheet" href="css/todolist.css">
    <script src="js/todolist.js"></script>
</head>

<body>
    <header>
        <section>
            <form action="javascript:;" id="form">
                <label for="title">ToDoList</label>
                <input type="text" id="title" name="title" placeholder="添加ToDo" required="required"
                    autofocus="autofocus" maxlength="40" autocomplete="off">
            </form>
        </section>
    </header>
    <section>
        <h2>正在进行 <span id="todocount"></span></h2>
        <ol id="todolist" class="demo-box">

        </ol>
        <h2>已经完成 <span id="donecount"></span></h2>
        <ul id="donelist">
           
        </ul>
    </section>
    <footer>
        Copyright &copy; 2014 todolist.cn &nbsp;<a href="javascript: void(0);" class="clear">clear</a>
    </footer>
</body>

</html>
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background-color: #cdcdcd;
    font-family: 'Courier New', Courier, monospace;
}

button,
input {
    border: 0;
}

label {
    cursor: pointer;
}

header {
    width: 100vw;
    height: 52px;
    background-color: #323232;
}

header section {
    color: #ddd;
    font-size: 24px;
}

header section input {
    float: right;
    width: 60%;
    height: 27px;
    padding-left: 6px;
    margin-top: 13px;
    border-radius: 6px;
    border: 1px solid #ccc;
    box-shadow: 0 0 6px -2px rgba(0, 0, 0, .9) inset;
    outline: none;
}

section {
    width: 650px;
    height: 52px;
    line-height: 52px;
    margin: 0 auto;
}

li {
    overflow: hidden;
    text-overflow: ellipsis;
    position: relative;
    width: 100%;
    height: 40px;
    line-height: 40px;
    font-size: 16px;
    background-color: #fff;
    border: 1px solid #ccc; 
    border-radius: 4px;
    cursor: move;
    list-style: none;
    margin-top: 10px;
    box-shadow: 0 6px 6px -1px rgba(0, 0, 0, .2);
    color: #666;
}

li::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 6px;
    height: 40px;
    background-color: #629a9c;
    border-radius: 4px 0 0 4px;
    border: 1px solid #ccc;
}

li input {
    position: absolute;
    top: 8px;
    left: 24px;
    width: 24px;
    height: 24px;
    cursor:pointer;
    opacity: .6;
    outline: none;
}

li input[type='text'] {
    width: 64%;
    height: 24px;
    font-size: 14px;
    font-family: 'Courier New', Courier, monospace;
    color: #000;
    border: 1px solid #4f4f4f;
    border-radius: 4px;
    margin-left: 40px;
    padding: 8px;
}


li p {
    margin-left: 72px;
}

li a {
    position: absolute;
    top: 7px;
    right: 8px;
    width: 24px;
    height: 24px;
    line-height: 24px;
    text-align: center;
    color: #fff;
    font-size: 14px;
    font-weight: bold;
    background-color: #999;
    border-radius: 50%;
    text-decoration: none;
}

#donelist li {
    background-color: #e6e6e6;
    color: #676767;
    opacity: .9;
}

section h2 {
    position: relative;
    margin: 20px 0;
}

#todocount,
#donecount {
    position: absolute;
    top: 16px;
    right: 0;
    width: 24px;
    height: 24px;
    line-height: 22px;
    text-align: center;
    font-size: 12px;
    color: #666;
    background-color: #e6e6fa;
    border: 1px solid #ccc;
    border-radius: 50%;
}


#donelist li::before {
    background-color: #b3b3b3;
}

footer {
    position: fixed;
    top: 90%;
    left: 50%;
    margin-top: 20px;
    color: #666;
    font-size: 14px;
    transform: translateX(-50%);
}

footer a {
    text-decoration: none;
    color: #999;
}
window.addEventListener('load', function () {
    var ipt = document.querySelector('input') // 获取input输入框
    var ol = document.querySelector('#todolist') // 获取正在进行下面的ol
    var ul = document.querySelector('#donelist') // 获取已经完成下面的ul
    var ToDoList = document.querySelector('label') // 获取ToDoList
    console.log(ToDoList);
    // 获取正在进行数量统计
    var todocount = document.querySelector('#todocount')
    // 获取已经完成数量统计
    var donecount = document.querySelector('#donecount')
    // 获取clear清除所有数据链接
    var clear = document.querySelector('.clear')
    // 定义全局变量
    var index = 0;
    var li = null;
    var checkbox = null;
    var p = null;
    var as = null;
    var text = null;
    
    // 禁止用户选中
    document.addEventListener('selectstart', function(e) {
        e.preventDefault();
    })
    
    // 点击ToDoList之后刷新页面
    ToDoList.addEventListener('click', function() {
        location.reload()
    })
    
    // 页面加载完毕时调用一次页面渲染函数
    load()
    
    
    ipt.addEventListener('keyup', function (e) {
        // 判断用户是否按下了回车键
        if (e.keyCode === 13) {
            // 判断输入框内容是否为空
            if (ipt.value !== '') {
                // 获取本地存储的数据
                var data = getDate('todolist')
                // console.log(data);
                // 将输入框中的内容取过来插入给data
                data.push({ title: this.value, done: false })
                // console.log(data);
                // 将data里面的数据保存进本地存储
                setDate('todolist', data)
                // 重新渲染页面
                load()
                // 清空输入框内容
                ipt.value = ''

            } else {
                // 如果为空弹出警示框, 提醒用户输入内容
                alert('请输入内容啦!笨蛋!')
            }
        }
    })

    // 清除本地存储所有数据
    clear.addEventListener('click', function () {
        localStorage.clear()
        // 重新渲染页面
        load()
    })

    // 读取本地存储数据函数
    function getDate(name) {
        var data = localStorage.getItem(name)
        // console.log(data);
        if (data !== null) {
            return JSON.parse(data)
        } else {
            return []
        }
    }
    
    // 保存本地存储数据函数
    function setDate(name, ele) {
        localStorage.setItem(name, JSON.stringify(ele))
    }
    
    // 页面渲染函数
    function load() {
        // 获取本地存储的数据
        var date = getDate('todolist')
        // 声明变量统计正在进行的数量
        var todoCount = 0;
        // 声明变量统计已经完成的数量
        var doneCount = 0;
        // console.log(date);
        // 每次生成元素渲染页面时, 清空ol和ul里面的内容
        ol.innerHTML = ''
        ul.innerHTML = ''
        // 动态生成元素函数
        function generate(ele) {
            // 动态生成li、input、p以及a标签
            li = document.createElement('li')
            checkbox = document.createElement('input');
            p = document.createElement('p')
            as = document.createElement('a')
            // 为每个a链接的href做赋值操作
            as.href = 'javascript: void(0);';
            as.innerHTML = '×' // 每个a链接的内容设置为×
            // 默认隐藏a
            as.style.display = 'none'
            // 将本地存储数组对象中所有title属性的值赋值给p
            p.innerHTML = date[i].title
            // 设置input的样式为复选框
            checkbox.type = 'checkbox'
            // 将复选框插入到li的最前面
            li.insertBefore(checkbox, li.children[0])
            // 将p插入到复选框的后面
            li.insertBefore(p, li.children[1])
            // 将a插入到li的最后面
            li.appendChild(as)
            // 将生成的li插入到ele里面
            ele.insertBefore(li, ele.children[0])
        }

        for (var i = 0; i < date.length; i++) {
            // console.log(date[i].done);
            // generate(ol)
            // 判断本地存储数据中的done是否为true, 如果是就生成元素到ul(已经完成)里面, 并且修改复选框为选定状态和让已经完成数量+1
            if (date[i].done) {
                generate(ul)
                checkbox.checked = true
                doneCount++

            } else {
                // 反之, 则生成元素到ol(正在进行)中也让正在进行数量统计加1
                generate(ol)
                todoCount++
            }
            // 给a设置自定义属性
            as.setAttribute('data-index', i)

            // 为动态生成的a链接绑定点击事件, 注意一定要写在创建a链接的for循环里
            as.addEventListener('click', function () {
                // 得到当前a链接的索引号
                index = this.getAttribute('data-index')
                // console.log(index);
                // 删除当前a链接对应的本地存储数据
                date.splice(index, 1)
                // 将删除后的数组重新赋值给本地存储
                // console.log(date[index]);
                setDate('todolist', date)
                // 重新渲染页面
                load()
            })
            // 为动态生成的复选框绑定点击事件, 注意点同上面a链接
            checkbox.addEventListener('change', function () {
                // console.log(this.checked);
                // console.log(date);
                // 得到当前a链接的索引号
                var index = this.nextElementSibling.nextElementSibling.getAttribute('data-index')
                // console.log(index);
                // console.log(date[index].done);
                // 将对应本地存储数据的done属性修改为当前复选框的选定状态
                date[index].done = this.checked
                setDate('todolist', date)
                load()
            })
            // 给每个li标签绑定点击事件
            li.addEventListener('click', function () {
                // 创建input标签
                text = document.createElement('input')
                // 获取当前li标签所在的a的索引号, 从而得到li的索引
                var index = this.children[2].getAttribute('data-index')
                // 获取当前li里面的p元素
                var p = this.children[1];
                // 获取当前li里面所有的input标签
                var texts = this.querySelectorAll('input');
                // 判断input标签个数, 如果小于2则像li里面插入text
                if (texts.length < 2) {
                    this.insertBefore(text, this.children[2])
                }
                // console.log(p);
                // console.log(index);
                // 将p元素的内容赋值给text的value
                text.value = p.innerHTML
                // 设置input样式为文本框
                text.type = 'text'
                // 自动获取焦点
                text.focus()
                // 将input内部光标定位到input内容部分最后面
                text.selectionStart = text.selectionEnd = text.value.length; // 这一步虽然是多余的但是是本着练习语法的心态写上的,欸嘿~
                // 默认全选中input内容
                // text.select();
                // this.replaceChild(text, p)  // text替换p元素
                // 隐藏p元素
                p.style.display = 'none'
                // 选中内容从最前面到内容长度的一半位置	
                // text.setSelectionRange(0, text.value.length / 2)
                text.addEventListener('blur', function () {
                    // 如果当前的表单的值不为空就修改数据
                    if (this.value != '') {
						date[index].title = this.value;
                    	// 将修改之后的数据插入本地存储
                    	setDate('todolist', date)
                    	load()
                    } else {
						alert('请输入内容啦!笨蛋!');
                        load();
                    }

                })
                // 文本框绑定键盘事件
                text.addEventListener('keyup', function (e) {
                    // 用户按下回车就执行以下代码
                    if (e.keyCode === 13) {
                        // 将修改之后的数据插入本地存储
                        setDate('todolist', date)
                        load();            
                    }
                })
            })
            // 鼠标经过li显示a
            li.addEventListener('mouseover', function () {
                this.lastElementChild.style.display = 'block'
            })
            // 鼠标经过li隐藏a
            li.addEventListener('mouseout', function () {
                this.lastElementChild.style.display = 'none'
            })
        }
        // 将计算好的数量统计分别赋值给正在进行和已经完成
        donecount.innerHTML = doneCount
        todocount.innerHTML = todoCount
    }
})

数据可视化项目

1. 什么是数据可视化

1.1 数据可视化

  • 数据可视化主要目的:借助于图形化手段,清晰有效地传达与沟通信息。
  • 数据可视化可以把数据从冰冷的数字转换成图形,揭示蕴含在数据中的规律和道理。

1.2 数据可视化的场景

目前互联网公司通常有这么几个大类的可视化需求:

  1. 通用报表
  2. 移动端图表
  3. 大屏可视化
  4. 图编辑&图分析
  5. 地里可视化

1.3 常见的数据可视化库

  • D3.js 目前Web端评价最高的JavaScript可视化工具库(入手难)
  • ECharts.js 百度出品的一个开源JavaScript数据可视化库
  • Highcharts.js 国外的前端数据可视化库,非商用免费,被许多国外大公司所使用
  • AntV 蚂蚁金服全新一代数据可视化解决方案
  • 等等

Highcharts和ECharts就像是Office和WPS的关系

1.4 小结

  • 数据可视化主要目的:借助于图形化手段,清晰有效地传达与沟通信息
  • 数据可视化在我们互联网公司中经常用于通用数据报表,移动端图表,大屏可视化,图编辑等
  • 数据可视化库有很多,接下来我们重点学习ECharts

2. 数据可视化项目概述

2.1 项目目的

市场需求:

应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生成数据,用户数据)下使用,**可视化图标**来展示提现数据,让数据更加直观,数据特点更加突出

学习阶段需求:

项目对我们同学来说,起着**承上启下**的作用。

承上

  • 复习以前学习内容
  • HTML5 + CSS3 布局相关技术
  • JavaScript \ jQuery 相关技术

启下

  • 为学习服务器编程做铺垫
  • 如果把服务器里面的数据渲染到页面中?

2.3 项目技术

  • HTML5 + CSS3 布局
  • CSS3动画、渐变
  • flex布局和rem适配方案
  • 图片边框border-image
  • ES6模板字符
  • ECharts可视化库等等

2.4 小结

  • 数据可视化项目展示
  • 学习这个项目的目的:市场需求和学习阶段需求
  • 项目用到的技术:以前学习过的技术和新技术
    • CSS3动画、渐变
    • jQuery库 + 原生JavaScript
    • flex布局和rem适配方案
    • 图片边框border-image
    • ES6模板字符
    • ECharts可视化库等等

3. ECharts简介

ECharts是一个使用JavaScript实现的开源可视化库,可以流畅的运行在pc和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图标。

官网地址:https://www.echartsjs.com/zh/index.html

官网地址:https://echarts.apache.org

  • 丰富的可视化类型
  • 多种数据格式支持
  • 流数据的支持
  • 移动端优化
  • 跨平台使用
  • 绚丽的特效
  • 详细的文档说明

4. ECharts的基本使用

4.1 ECharts使用五步曲

注意:这里只要求记住ECharts使用的步骤,具体修改定制稍后会讲。

  • 步骤一:下载并引入echarts.js文件 ——————-> 图标依赖这个js库

  • 步骤二:准备一个具备大小的DOM容器 ———————-> 生成的图标会放入这个容器

  • 步骤三:初始化echarts实例对象 ————————–> 实例化echarts对象

  • 步骤四:指定配置项和数据(option)————————–> 根据具体需求修改配置选项

  • 步骤五:将配置设置得echarts实例对象 ———————–> 让echarts对象根据修改好的配置生效

  • 准备一个具备大小的DOM容器

 <div id="main" style="width: 600px; height: 400px;"></div>
  • 初始化echarts实例对象
	var myChart = echarts.init(document.getElementById('main'));
  • 指定配置项和数据
	// 指定图表的配置项和数据
      var option = {
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
          data: ['销量']
        },
        xAxis: {
          data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
        },
        yAxis: {},
        series: [
          {
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      };

4.3 相关配置讲解

  • title:标题组件
  • tooltip:提示框组件
  • legend:图例组件
  • grid:直角坐标系内绘图网格
  • XAxis:直角坐标系grid中的x轴
  • yAxis:直角坐标系grid中的y轴
  • series:系列列表。每个系列通过type决定自己的图表类型(什么类型的图表)
  • color:调色盘颜色列表

先了解以上9个配置的作用,其余配置还有具体细节通过查阅文档:文档菜单-配置项手册

学echarts关键在于学会查阅文档,根据需求修改配置

	grid: {
        left: '0%',
        right: '0%',
        bottom: '3%',
        // 当刻度标签溢出的时候,grid区域是否包含坐标轴的刻度标签。如果为true则显示
        // 如果left right等设置为0%,刻度标签就溢出了,此时决定是否显示刻度标签
        containLabel: true
    }
series:系列列表
  • type:类型(什么类型的图表)比如line是折线bar柱形等

  • name:系列名称,用于tooltip的显示,legend的图例筛选变化

  • stack:数据堆叠。如果设置相同值,则会数据堆叠。

    • 数据堆叠:第二个数据值 = 第一个数据值 + 第二个数据值

      ​ 第三个数据值 = 第二个数据值 + 第三个数据值…. 依次叠加

      如果给stack指定不同值或者去掉这个属性则不会发生数据堆叠

5. 数据可视化项目适配方案

5.1 项目需求

  • 设计稿是1920px
  • PC端适配:宽度在1024~1920之间页面元素宽高自适应

5.2 适配方案

flexible.js

检测浏览器宽度

修改html文字大小

rem单位

页面元素根据rem适配大小

配合cssrem插件

flex布局

页面快速布局

  • flexible.js把屏幕分为24等份
  • PC端的效果图是1920px
  • cssrem插件的基准值是80px
  • rem值自动生成

要把屏幕约束在1024~1920之间有适配

	@media screen and (max-width: 1024px) {
            html {
                font-size: 42.66px!important;
            }
        }

        @media screen and (min-width: 1920px) {
            html {
                font-size: 80px!important;
            }
        }

6. 数据可视化项目开发

6.1 边框图片

  1. 边框图片的使用场景
  2. 边框图片的切图原理
  3. 边框图片语法规范

盒子大小不一,但是边框样式相同,此时就需要边框图片来完成

为了实现丰富多彩的边框效果,在CSS3中,新增了border-image属性,这个新属性允许指定一幅图像作为元素的边框。

1. 边框图片切图原理:(重要)

把四个角切出去(九宫格的由来),中间部分可以铺排,拉伸或者环绕。

2. 边框图片语法:(重要)
属性描述
border-image-source用在边框的图片的路径。(哪个图片?)
border-image-slice图片边框向内偏移。(裁剪的尺寸,一定不加单位,上右下左顺序)
border-image-width图片边框的宽度(需要加单位)(不是边框的宽度,是边框图片的宽度)
border-image-repeat图像边框是否应平铺(repeat)、铺满(round)或拉伸(stretch)默认拉伸

6.2 公共面板样式开发

面板类:.panel

border-image-slice:按照 上右下左顺序切割

/* 公共面板样式 */
.panel {
    position: relative;
    border: 15px solid transparent;
    border-width: .6375rem .475rem .25rem 1.65rem;
    border-image-source: url(../images/border.png);
    border-image-slice: 51 38 20 132;
    margin-bottom: .25rem;
}   

.panle h3 {
    color: #fff;
    font-size: .35rem;
    margin-bottom: .15rem;
}

.inner {
    position: absolute;
    top: -.6375rem;
    right: -.475rem;
    bottom: -0.25rem;
    left: -1.65rem;
    padding: .3rem .45rem;
}

6.3 通过类名调用字体图标

  1. HTML页面引入字体图标中css文件。
  2. 标签直接调用图标对应的类名即可。(类名在css文件中标注)

引入css文件和声明字体图标的时候,一定注意路径问题。

6.4 立即执行函数用法

JS文件中,会有大量的变量命名,特别是ECharts使用中,需要大量初始化ECharts对象?

为了防止变量名冲突(变量污染)我们采用立即执行函数策略:

	(function () {}) ();
	(function () {
        var num = 10;
    }) ();
	(function () {
        var num = 10;
    }) ();

6.5 无缝滚动原理

  1. 先克隆marquee里面所有的行(row)
  2. 通过css3动画滚动marquee
  3. 鼠标经过marquee就停止动画
	animation-play-state: paused;
 		// 先克隆marquee里面所有的行(row)
        var rows = null;
        var newRows = null;
        for (var i = 0; i < marquees.length; i++) {
            rows = marquees[i].querySelectorAll('.row')
            for (var j = 0; j < rows.length; j++) {
                newRows = rows[j].cloneNode(true);
                marquees[i].appendChild(newRows);
            }
        }
.marquee-view .marquee {
    position: absolute;
    display: flex;
    flex-direction: column;
    width: 100%;
    animation: move 15s linear  infinite;
}

/* 通过css3动画滚动marquee */
@keyframes move {
    from {}
    to {
        transform: translateY(-50%);
    }
}

/* 鼠标经过marquee就停止动画 */
.marquee-view .marquee:hover {
    animation-play-state: paused;
}

6.6 点位分析模块-使用ECharts图表

  1. 先官网找到类似的图表引入到页面中
  2. 根据需求修改具体的配置

1. 引入图表
// 点位分布统计模块
    (function() {
        // 1. 实例化ECharts对象
        var myCharts = echarts.init(document.querySelector('.pie'));
        // 2. 指定配置项和数据
        var option = {
            tooltip: {
              trigger: 'item',
              formatter: '{a} <br/>{b} : {c} ({d}%)'
            },
            series: [
              {
                name: 'Area Mode',
                type: 'pie',
                radius: [20, 140],
                center: ['75%', '50%'],
                roseType: 'area',
                itemStyle: {
                  borderRadius: 5
                },
                data: [
                  { value: 30, name: 'rose 1' },
                  { value: 28, name: 'rose 2' },
                  { value: 26, name: 'rose 3' },
                  { value: 24, name: 'rose 4' },
                  { value: 22, name: 'rose 5' },
                  { value: 20, name: 'rose 6' },
                  { value: 18, name: 'rose 7' },
                  { value: 16, name: 'rose 8' }
                ]
              }
            ]
          };
        // 3. 配置项和数据给实例化对象
        myCharts.setOption(option);
    }) ();
2. 定制图表
第一步:参考官方列子,熟悉里面参数具体含义
var option = {
            tooltip: {
              trigger: 'item',
              formatter: '{a} <br/>{b} : {c} ({d}%)'
            },
            series: [
              {
                name: 'Area Mode',
                type: 'pie',
                radius: [20, 140],
                center: ['75%', '50%'],
                roseType: 'area',
                itemStyle: {
                  borderRadius: 5
                },
                data: [
                  { value: 30, name: 'rose 1' },
                  { value: 28, name: 'rose 2' },
                  { value: 26, name: 'rose 3' },
                  { value: 24, name: 'rose 4' },
                  { value: 22, name: 'rose 5' },
                  { value: 20, name: 'rose 6' },
                  { value: 18, name: 'rose 7' },
                  { value: 16, name: 'rose 8' }
                ]
              }
            ]
          };
第二步:按照需求定制
  • 需求1:颜色设置
	color : ['#006cff', '#60cda0', '#ed8884', '#ff9f7f', '#0096ff', '#32c5e9', '#1d9dff']
  • 需求2:修改饼形图大小(series对象)
	radius: ['10%', '70%'],
  • 需求3:数据使用更换(series对象里面data对象)
						{ value: 20, name: '云南' },
                        { value: 26, name: '北京' },
                        { value: 24, name: '山东' },
                        { value: 25, name: '河北' },
                        { value: 20, name: '江苏' },
                        { value: 25, name: '浙江' },
                        { value: 30, name: '四川' },
                        { value: 42, name: '湖北' }
  • 需求4:字体略小些 10px (series对象里面设置)

​ 饼图图形上的文本标签可以控制饼形图的文字的一些样式。 label对象设置

series: [
                {
                    // 图表名称
                    name: '点位统计',
                    // 图表类型
                    type: 'pie',
                    // 南丁格尔玫瑰图有两个圆,内圆半径10%,外圆半径70%
                    // 饼形图半径。可以是像素,也可以是百分比(基于图表bom容器的半径 第一项是内半径,第二项是外半径)
                    radius: ['10%', '70%'],
                    // 图表中心位置left 50% top 50% 距离图表DOM容器
                    center: ['50%', '50%'],
                    // radius 半径模式,另外一种是area面积模式
                    roseType: 'radius',
                    // 数据集 value 数据的值 name 数据的名称
                    data: [
                        { value: 20, name: '云南' },
                        { value: 26, name: '北京' },
                        { value: 24, name: '山东' },
                        { value: 25, name: '河北' },
                        { value: 20, name: '江苏' },
                        { value: 25, name: '浙江' },
                        { value: 30, name: '四川' },
                        { value: 42, name: '湖北' }
                    ],
                    // 文本标签控制饼形图文字的相关样式,注意它是一个对象
                    label: {
                        fontSize: 10
                    }
                }
            ]
  • 需求5:防止缩放的时候,引导性过长。引导线略短些(series对象里面的labelLine对象设置)
    • 连接图表6px
    • 连接文字8px
	// 引导线调整
    labelLine: {
       // 连接扇形图线长
       length: 6,
       // 连接文字线长
       length2: 8
    }

6.7 ES6模板文字

 var star = {
        name: "刘德华",
        age: 18
      };
      //   以前的写法 拼接的时候引号很容易出问题
      console.log("我的名字是" + star.name + "我的年龄是" + star.age);
      //   ES6 模板字符写法
      console.log(`我的名字是${star.name}
      我的年龄是${star.age}`);
      console.log(`<span>${star.name}</span><span>${star.age}</span>`);
    </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值