遍历DOM并修改CSS时,为何后续元素的样式都会变成第一个元素的样式?

起因

目前想做一个拖拽排序且带动画的功能,经过考虑和参考后,发现大部分解决方法都是将元素改为绝对定位,之后通过 transition 或别的方式插值处理 left 和 top,于是测试后这种方式。

问题

但编码中发现一个问题,使用 js 或 jQuery 遍历并修改元素css时,会出现所有元素被赋予第一个元素的CSS样式。
当不修改CSS时,打印的css变量均正常,但如果修改,则后续所有变量值都会变成第一次的值。
不知该问题为什么引起又如何解决?

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            box-sizing: border-box;
        }

        .container {
            display: flex;
            width: 100%;
            flex-wrap: wrap;
            position: relative;
        }

        .box {
            flex: 0 0 25%;
            border: 1px solid #999;
            margin-top: 10px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="box" draggable="true">1</div>
        <div class="box" draggable="true">2</div>
        <div class="box" draggable="true">3</div>
        <div class="box" draggable="true">4</div>
        <div class="box" draggable="true">5</div>
        <div class="box" draggable="true">6</div>
        <div class="box" draggable="true">7</div>
        <div class="box" draggable="true">8</div>
        <div class="box" draggable="true">9</div>
        <div class="box" draggable="true">10</div>
        <div class="box" draggable="true">11</div>
        <div class="box" draggable="true">12</div>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script>
        $(document).ready(function () {
            $('.box').each(function () {
                var css = {
                    'position': 'absolute',
                    'left': $(this)[0].offsetLeft,
                    'top': $(this)[0].offsetTop,
                    'width': $(this)[0].offsetWidth,
                    'height': $(this)[0].offsetHeight
                };
                console.log(css);
                $(this).removeClass('box');
                // 如果执行下面一行,则所有的 css 内容都会变成第一个元素的值
                // position:absolute;left: 0px; top: 10px;width: 226px;height: 23px
                $(this).css(css);
            });
        });
    </script>
</body>

</html>

原因与解决方案

几天后我回顾了此问题,将自己最终的理解重新整理。

该问题的重点,在于jQuerycss()方法或javascript修改style都会立即触发重排

修改css并移除box后,浏览器已经立即将第二项移动到了第一项的位置。

每次修改css后都会触发重排,所以解决方案就是在取到所有css数据后(循环结束后),再修改样式。
也就是:

$('.box').each(function () {
    var css = {
        'position': 'absolute',
        'left': $(this)[0].offsetLeft,
        'top': $(this)[0].offsetTop,
        'width': $(this)[0].offsetWidth,
        'height': $(this)[0].offsetHeight
    };
    var that = $(this);
    setTimeout(function () {
        that.css(css);
        that.removeClass('box');
    }, 0);
});
阅读 3.2k
2 个回答

你有10个盒子累起来了,你决定抽调最后一个把它粘在墙上,然后其余9个都掉下来了, 所以每次你获取的最后一个盒子位置总是一样的, 因为他们总是在最底部.

解决方案就是不要立即抽调最后一个盒子,把尺寸都测量完后在执行抽出的操作.

function queueUpdate($el, css) {
  setTimeout(function () {
    $el.css(css)
  }, 0)
}

$(document).ready(function () {
  $('.box').each(function () {
    var css = {
      'position': 'absolute',
      'left': $(this)[0].offsetLeft,
      'top': $(this)[0].offsetTop,
      'width': $(this)[0].offsetWidth,
      'height': $(this)[0].offsetHeight
    };
    $(this).removeClass('box');
    
    // 如果执行下面一行,则所有的 css 内容都会变成第一个元素的值
    // position:absolute;left: 0px; top: 10px;width: 226px;height: 23px
    queueUpdate($(this), css)
  });
});

执行$(this).css(css);这个后你的$(this)[0].offsetLeft,这些都发生变化了

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题