offset相关属性和匀速动画

offset家族的组成

offset 的中文是偏移,补偿,位移。

offset相关的属性包括:

  • offsetWidth
  • offsetHeight
  • offsetLeft
  • offsetTop
  • offsetParent

offsetWidth和offsetHeight

offsetWidthoffsetHeight 的值为:元素的宽/高 + padding + border的宽度。

<style>
    .box {
        box-sizing: content-box;
        width: 100px;
        height: 100px;
        margin: 10px;
        border: 10px solid #000;
        padding: 10px;
    }
</style>

<div class="box"></div>
<script>
    const box = document.querySelector('.box');
    console.log(box.offsetWidth);			// 140
    console.log(typeof box.offsetWidth);	//number
</script>

offsetParent

offsetParent 获取的是元素的定位父元素

  • 如果当前元素的父元素,有CSS定位(position为absolute、relative、fixed),那么 offsetParent 获取的是最近的那个父元素。

  • 如果当前元素的父元素,没有CSS定位(position为absolute、relative、fixed),那么offsetParent 获取的是body

<div class="box1" style="position: absolute;">
    <div class="box2" style="position: relative;">
        <div class="box3"></div>
    </div>
</div>
<script>
    const box3 = document.querySelector('.box3');
    console.log(box3.offsetParent);
</script>

输出为:

image-20210622222618979

offsetLeft和offsetTop

offsetLeftoffsetTop 的值为:相对于元素的offsetParent(定位父元素)的水平/垂直偏移量

备注:从父元素的 padding 开始算起,父元素的 border 不算在内。

<style>
    .box1 {
        position: relative;
        width: 200px;
        height: 200px;
        border: 100px solid #000;
        margin: 100px;
        padding: 100px;
    }
    .box2 {
        width: 100px;
        height: 100px;
    }
</style>

<div class="box1">
    <div class="box2" style="left: 20px;"></div>
</div>
<script>
    const box2 = document.querySelector('.box2');
    console.log(box2.offsetLeft);		//100
    console.log(box2.style.left);		//20px
</script>

如果当前元素的父元素position为relative,自己的position为absolute时,offset == style.left(去掉px)

offsetLeft 和 style.Left 的区别

  1. 最大区别:

offsetLeft 可以返回无定位父元素的偏移量。如果父元素中都没有定位,则body为准。

style.left 只能获取行内样式(写在标签内),如果定义在style内或在外部样式表内,则返回空字符串。

  1. offsetTop 返回的是数字,而 style.top 返回的是字符串,而且还带有单位:px。
  2. offsetLeft 和 offsetTop 只读,而 style.left 和 style.top 可读写(只读是获取值,可写是修改值)

总结:我们一般的做法是:用offsetLeft 和 offsetTop 获取值,用style.left 和 style.top 赋值(比较方便)。理由如下:

  • style.left:只能获取行内式,获取的值可能为空,容易出现NaN。
  • offsetLeft:获取值特别方便,而且是现成的number,方便计算。它是只读的,不能赋值。

动画的种类

  • 闪现(基本不用)
  • 匀速
  • 缓动

简单举例如下:(每隔500ms,盒子享有移动50px)

<!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;
            width: 100px;
            height: 100px;
            background-color: pink;
        }
    </style>
</head>
<body>
    <button>动画</button>
    <div></div>
    <script>
        const button = document.querySelector('button');
        const div = document.querySelector('div');
        button.onclick = function() {
            setInterval(function() {
                div.style.left = div.offsetLeft + 50 + 'px';
            }, 500);
        };
    </script>
</body>
</html>

动画1

匀速动画:每隔50ms,移动盒子10px

<!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>
        .container {
            position: relative;
        }
        .box1 {
            position: absolute;
            top: 30px;
            left: 195px;
            width: 100px;
            height: 100px;
            background-color: pink;
        }
        .box2 {
            position: absolute;
            top: 140px;
            width: 100px;
            height: 100px;
            background-color: aquamarine;
        }
    </style>
</head>
<body>
    <div class="container">
        <button>移动到 left:200px</button>
        <button>移动到 left:400px</button>
        <div class="box1"></div>
        <div class="box2"></div>
    </div>
    <script>
        const button1 = document.getElementsByTagName('button')[0];
        const button2 = document.getElementsByTagName('button')[1];
        const box1 = document.getElementsByClassName('box1')[0];
        const box2 = document.getElementsByClassName('box2')[0];
        button1.onclick = function() {
            animate(box1, 200);
            animate(box2, 200);
        };
        button2.onclick = function() {
            animate(box1, 400);
            animate(box2, 400);
        }

        // 每隔50ms,移动10px
        function animate(ele, target) {
            // clearInterval(ele.timer);
            // speed为步长,如果目标值大于当前值,向右移动
            // 如果目标值小于当前值,向左移动
            let speed = target > ele.offsetLeft ? 10 : -10;
            ele.timer = setInterval(function() {
                // 计算目标值和当前值的距离
                let value = target - ele.offsetLeft;
                // 如果计算值小于步长,直接到达目的地,并清除计时器,否则每次移动10px
                if(Math.abs(value) < Math.abs(speed)) {
                    ele.style.left = target + 'px';
                    clearInterval(ele.timer);
                } else {
                    ele.style.left = ele.offsetLeft + speed + 'px';
                }
            }, 50);
        }
    </script>
</body>
</html>

动画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>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            list-style: none;
        }
        .all {
            position: relative;
            width: 500px;
            height: 200px;
            padding: 7px;
            margin: 100px auto;
            border: 1px solid #ccc;
        }
        .screen {
            position: relative;
            width: 500px;
            height: 200px;
            overflow: hidden;
        }
        .screen ul {
            position: absolute;
            left: 0;
            top: 0;
            width: 3000px;
        }
        .screen li {
            float: left;
            overflow: hidden;
            width: 500px;
            height: 200px;
        }
        .screen ol {
            position: absolute;
            right: 10px;
            bottom: 10px;
            line-height: 20px;
        }
        .screen ol li {
            float: left;
            width: 20px;
            height: 20px;
            border: 1px solid #ccc;
            margin-left: 10px;
            text-align: center;
            background-color: #fff;
            cursor: pointer;
        }
        .screen ol li.current {
            background-color: yellow;
        }
        #arr {
            display: none;
        }
        #arr span {
            position: absolute;
            width: 40px;
            height: 40px;
            left: 5px;
            top: 50%;
            margin-top: -20px;
            background-color: #000;
            opacity: 0.3;
            border: 1px solid #fff;
            text-align: center;
            color: #fff;
            font-size: 30px;
            cursor: pointer;
        }
        #arr #right {
            right: 5px;
            left: auto;
        }
    </style>
</head>
<body>
    <div id="all" class="all">
        <div class="screen">
            <ul>
                <li><img src="image/1.jpg" alt="pic1" width="500" height="200"></li>
                <li><img src="image/2.jpg" alt="pic1" width="500" height="200"></li>
                <li><img src="image/3.jpeg" alt="pic1" width="500" height="200"></li>
                <li><img src="image/4.jpeg" alt="pic1" width="500" height="200"></li>
            </ul>
            <ol></ol>
            <div id="arr">
                <span id="left"><</span>
                <span id="right">></span>
            </div>
        </div>
    </div>
    <script>
        window.onload = function() {
            // 获取需要DOM操作的节点
            const all = document.getElementById('all');
            const screen = all.firstElementChild || all.firstChild;
            const imgWidth = screen.offsetWidth;
            const ul = screen.firstElementChild || screen.firstChild;
            const ol = screen.children[1];
            const spanArr = screen.lastElementChild || screen.lastChild;

            // 复制ul中的第一个li节点,添加到ul的最后面
            const cloneLi = ul.children[0].cloneNode(true);
            ul.appendChild(cloneLi);

            // 有多少张图片就在ol中添加多少个li
            // 注意要减掉1,因为第一张和最后一张是相同的
            // 最后,点亮第一li
            for(let i = 0; i < ul.children.length - 1; i++) {
                let ele = document.createElement('li');
                ele.innerText = i + 1;
                ol.appendChild(ele);
            }
            const olChildren = ol.children;
            olChildren[0].className = 'current';

            // 鼠标放到ol的li上切换图片
            for(let i = 0; i < olChildren.length; i++) {
                olChildren[i].index = i;
                olChildren[i].onmouseover = function() {
                    // 去掉所有li上的类名
                    for(let i = 0; i < olChildren.length; i++) {
                        olChildren[i].className = '';
                    }
                    // 点亮当前li
                    this.className = 'current';
                    key = square = this.index;
                    animate(ul, -this.index * imgWidth);
                }
            }

            // 两个计数器,第一个记录图片,第二个记录小方块
            let key = 0;
            let square = 0;
            // 添加一个定时器
            let timer = setInterval(autoPlay, 1000);

            // 鼠标放上去清除定时器,移开后开启定时器
            all.onmouseover = function() {
                spanArr.style.display = 'block';
                clearInterval(timer);
            };
            all.onmouseout = function() {
                spanArr.style.display = 'none';
                timer = setInterval(autoPlay, 1000);
            }

            spanArr.children[0].onclick = function() {
                key--;
                // 如果是第一张图片,先跳转到最后一张图片(第一张和最后一张是相同的图片),
                // 把key置为length - 1,也就是倒数第二张图片的key
                // 然后animate滑动到倒数第二张
                if(key < 0) {
                    ul.style.left = -imgWidth * (olChildren.length) + 'px';
                    key  = olChildren.length - 1;
                }
                animate(ul, -key * imgWidth);
                square--;
                // 如果是第一张图片,square--后变为-1,重新置为length - 1
                if(square < 0) {
                    square = olChildren.length - 1;
                }
                for(let i = 0; i < olChildren.length; i++) {
                    olChildren[i].className = '';
                }
                olChildren[square].className = 'current';
            };
            // 点击向右的箭头相当于执行一次autoPlay
            spanArr.children[1].onclick = function() {
                autoPlay();
            };

            function autoPlay() {
                key++;
                // 到了最后一张图片,跳转到第一张,把key置为1
                // 然后下面的animate滑动到第二张
                if(key > olChildren.length) {
                    ul.style.left = '0px';
                    key = 1;
                }
                animate(ul, -key * imgWidth);
                square++;
                // 等于5的时候变为0
                if(square > olChildren.length - 1) {
                    square = 0;
                }
                for(let i = 0; i < olChildren.length; i++) {
                    olChildren[i].className = '';
                }
                olChildren[square].className = 'current';
            }

            function animate(ele, target) {
                clearInterval(ele.timer);
                let speed = target > ele.offsetLeft ? 10 : -10;
                ele.timer = setInterval(function() {
                    let value = target - ele.offsetLeft;
                    if(Math.abs(value) < Math.abs(speed)) {
                        ele.style.left = target + 'px';
                        clearInterval(ele.timer);
                    } else {
                        ele.style.left = ele.offsetLeft + speed + 'px';
                    }
                }, 10);
            }
        }
    </script>
</body>
</html>

实现效果:

动画3

参考资料:https://github.com/qianguyihao/Web