Web前端之接近完美的跑马灯、绝对定位父元素高度塌陷问题、巧妙的计算速度、动态设置高度、走马灯动画、文本滚动、清除浮动、相对定位、四舍五入、流水灯、坍塌、translateX、absolute

222 篇文章 6 订阅
95 篇文章 0 订阅


前言

实现一个简单的跑马灯功能,原本觉得没什么,就是一个div从右往左移动就可以了。但是没想到真正动手起来,却发现很多细节问题,大概知识生疏了,一时也没想到如何解决,后来才想到解决方案。
这篇文章的跑马灯基本使用css实现,但也有少量的JavaScript。
父元素高度塌陷解析。


跑马灯的基本代码

先贴上基本代码(不含动画),下面的示例均以这些基本代码为基础进行修改。

* {
    margin: 0;
    padding: 0;
}

.marquee-container {
    width: 100%;
    height: 50px;
    line-height: 50px;
    background-color: cadetblue;
    overflow: hidden;
}

.marquee-box {
    display: inline-block;
    color: #fff;
    white-space: nowrap;
    animation: marquee 3s linear infinite;
}

<div class="marquee-container">
    <div class="marquee-box">
        <p>123456789</p>
    </div>
</div>

跑马灯的错误实现

看到很多博客都说使用css实现跑马灯的方法有两种,但我发现这两种方法都存在一定的问题。


错误方法一

.marquee-container {
    position: relative;
}

.marquee-box {
    position: absolute;
}

@keyframes marquee {
    0% {
        left: 100%;
    }

    100% {
        left: -100%;
    }
}

虽然动画可以动起来,但是只要仔细观察,就不难发现当内容体移出容器的最左边边界时,内容体并没有立刻重新出现在右边边界,也即是在某个时间段,内容体并不会展示出来。
这是因为100%动画帧的left: -100%;导致的,left: -100%;并不是指内容体相对于自身宽度的-100%的水平位移,而是指内容体相对于容器的宽度的-100%的水平位移,这意味着内容体并非移出容器外后立刻出现在起点。
即使能够接受内容体不会立刻重新出现右边边界的情况,这种写法依然是错误的,只要跑马灯内容宽度足够长时,跑马灯动画结束的地方可能落在容器内的某个位置,而不是边界或边界以外的地方。


错误方法二

@keyframes marquee {
    0% {
        transform: translateX(100%);
    }

    100% {
        transform: translateX(-100%);
    }
}

这段动画和错误方法一的动画的错误比较类似。首先当内容过短时,0%动画帧的translateX(100%)根本就不能以容器的右边界做为起点。这是因为translateX里的100%是相对内容体自己的宽度的100%的水平位移。
其次当内容宽度过长时,起点会离容器的右边界太远。


错误方法总结

由此可见,上面的两种错误方法主要有两个缺陷。
1、受到内容体的宽度影响,内容体过长或过短时,可能会引发bug。
2、无法精准定位内容体移动的起点或终点。


跑马灯的正确实现

改进一(不再受内容宽度影响,并且在特定场景精准定位内容体移动的起点和终点)

事实上,错误方法二的使用translateX的思路是正确的,只是0%动画帧的translateX(100%)需要改一下。只需要改为以下的代码就基本正确了。

@keyframes marquee {
    0% {
        transform: translateX(100vw);
    }

    100% {
        transform: translateX(-100%);
    }
}

100vw即视口的宽度,在这个场景里,容器的width100%,等同于视口宽度,也即是说内容体translateX(100vw)后肯定刚好落在容器的右边边界。
这个方法解决了两个问题,一个是不受内容体的长短影响,第二个是能够精准定位内容体移动的起点和终点。
但是,这个方法也存在一定缺陷,只适用于跑马灯容器的宽度等于视口宽度的情况。
原因是当容器宽度不等于视口宽度时,内容体translateX(100vw)后,它的移动起点永远落在视口的右边界外,不会再精准落在容器的右边界。


改进二(自适应不同的跑马灯容器宽度)

这个方法的实现比较巧妙,使用lefttranslate组合进行实现动画,无论你的跑马灯容器宽度如何,都可以进行自适应。

.marquee-container {
    position: relative;
}

.marquee-box {
    position: absolute;
}

@keyframes marquee {
    0% {
        left: 100%;
    }

    100% {
        left: 0%;
        transform: translateX(-100%);
    }
}

其实很简单,就是使用absolute+left进行实现最基本的移动,当元素从left: 100%;移动到left: 0%;时,内容体刚好位于容器的最左侧,但没有移出容器的左边界外,这里再加一个transform: translateX(-100%);,向左移动内容体100%的宽度,即可将内容体刚好移出跑马灯容器位置。
这里还存在一个问题,跑马灯的内容都是从接口请求数据,内容是动态变化,这说明内容宽度不一致。不同宽度的内容在相同的播放动画时间里,它们的移动速度是不一致的,过长的内容会移动得相对较快,过短的内容会移动得相对较慢。
所以,不论内容宽度如何,我们要求动画都要有一个恒定的移动速度,而不是恒定的动画时间。


改进三(动态改变动画时间,得到恒定的速度)

只有一个字的内容和有几百个字的内容在相同的动画时间里,肯定是前者速度极快,后者速度极慢,因为前者的位移量比后者的位移量要短很多,因此需要根据内容去动态改变动画时间。
首先,复习下初中物理的简单的位移公式: s = vt,s为位移,v为平均速度,t为时间。
现在要根据不同的内容宽度求一个动态时间,可得时间公式: t = s / v
先看下速度v,速度v其实就是我们要定义一个的恒定速度(以px单位,因为下面位移公式也是以px单位),可以假设定义为: 150px / s
然后看下位移s怎么求。
在此之前,先说一下为什么在该篇文章一直称跑马灯的文字内容长度内容宽度,而不是内容长度
因为如果用了内容长度这个词语,某些读者的思维可能会被引导到,求位移就是获取跑马灯的内容字符串,然后使用字符串.length进行去获取字符串长度,再根据字符串长度进行计算位移。但是这个所谓的字符串长度其实是字数,并非文字所在的dom实际宽度。
这里要用dom的实际宽度去求解位移的两个原因。
1、在等同的font-size中,一个数字与一个中文在实际的dom上所占的实际宽度是不一致的,但它们都可归为一个字,因此用字数去代表位移,肯定有误差。
2、没有人说过跑马灯内容只能显示纯文字,如果要在某个关键字里添加一个a链接,那是不是计算字数的时候要排除这个a标签。而且如果这里还可以添加emoji表情和图片呢?那是不是又要额外处理?
好了,回到正题。
位移s = 跑马灯内容宽度 * 2 + 跑马灯容器宽度
可以很简单的得到以下的动态计算动画时间的JavaScript代码。

<script>
    // 跑马灯容器宽度
    let containerWidth = document.querySelector('.marquee-container').offsetWidth;
    // 跑马灯内容宽度
    let boxWidth = document.querySelector('.marquee-container .marquee-box').offsetWidth;
    // 动画时间,这里我没有四舍五入,你可以进行四舍五入
    let duration = (boxWidth * 2 + containerWidth) / 150 + 's';

    document.querySelector('.marquee-container .marquee-box').style.cssText = 'animation-duration:' + duration;
</script>

不过,这段动画看起来似乎有一丢丢的抖动掉帧的感觉,我朋友也是这么说的,目前还没有解决的头绪。


完整代码

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

    .box {
        position: relative;
        width: 68px;
        margin: 0px auto;
        background-color: cadetblue;
        overflow: hidden;
    }

    .item {
        position: absolute;
        display: inline-block;
        padding: 2px 0px;
        color: #fff;
        white-space: nowrap;
    }

    @keyframes anim {
        0% {
            left: 100%;
        }

        100% {
            left: 0%;
            transform: translateX(-100%);
        }
    }
</style>

<div id="idBox" class="box">
    <div id="idItem" class="item"></div>
</div>

<script>
    const str = '程序、编程、写代码、互联网、物联网、技术栈、程序员、工程师、软件开发、硬件开发、计算机科技、智慧电子模块';

    function init(val = '更多关于我的消息,请关注“代码农”微信公众号。', speed = 150) {
        let len = val.length;

        idItem.textContent = val;
        setTimeout(() => {
            let height = idItem.offsetHeight;
            let idBoxW = idBox.getBoundingClientRect().width;
            let idItemW = idItem.getBoundingClientRect().width;
            let duration = (idItemW * 2 + idBoxW) / speed;

            duration = Math.round(duration);
            idBox.style.height = height + 'px';
            idItem.style.animation = `anim ${duration}s linear infinite`;
            // running paused
            idItem.style.animationPlayState = 'running';
        }, 0);
    }

    init(str);
</script>
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值