表单label跳动效果

申明:这是我在抖音的一位叫做"青牛前端"的博主上刷到的,我觉得效果非常炫酷,所以尝试模仿一下,并且解决了他的一个小问题(也可能是博主没有发进阶版),下面为效果展示图:

首先是静态页面布局,大致如下:

    <div class="form_div">
        <div class="form_item">
            <input type="text" name="username" id="username" autocomplete="off">
            <label for="username">username</label>
        </div>
        <div class="form_item">
            <input type="password" name="password" id="password" autocomplete="off">
            <label for="password">password</label>
        </div>
        <button class="submit">提交</button>
    </div>

有一个后续需要用到的小细节,为了能在input的聚焦伪类中能通过查找兄弟节点找到对应的元素,最好将label放在input框之下,因为查找兄弟节点总是查找位于自身之下的兄弟节点

css的一些思路:

  • input与label的布局是相对定位嵌套绝对定位的模式,慢慢调整位置
  • input框的内容区域的背景色,一开始我是直接设置跟登录框一样的颜色(默认为白色),但考虑到以后的使用颜色变化需要修改,于是直接将背景设置为透明background-color: transparent;
  • input框的聚焦的边框,它需要设置的属性为outline: none;

然后完成一个个跳动的字的动画,我们在label中输入的是一个完整的单词,需要使用js将一个单词拆分为一个个字母,这里使用split进行拆分,它的用法如下:

    const data="username"
    console.log(data.split(''))

知道了字母的处理方法之后,先获取label中的单词,再将每个label中的单词拆分为字母,使用span标签将字母进行组合,并将原先label中的单词进行文本覆盖,因为我们获取到的是一个数组,并且需要返回文本覆盖值,因此可以使用map处理数组。
跳动的实质是每个span的的动画,因此在生成的span中可以添加延迟的style,这里使用的是trasition-delay

        const labels = document.querySelectorAll('label')

        labels.forEach((label) => {
            label.innerHTML = label.innerText.split('').map((letter, index) => {
                return `<span style="transition-delay:${index * 50}ms;">${letter}</span>`
            }).join('')
        })

添加输入框的聚焦动画,先给对应的span添加3秒的动画transition:all .3s,跳动实际上就是span的向上平移,使用transition:translateY()修改span的位置,将这个修改位置的css写在input的聚焦中,这里就可以使用之前说的兄弟选择器,选择label下的span,在添加一些变化的样式,代码如下:

        .form_item label span {
            display: inline-block;
            transition: all .3s;
        }

        .form_item input:focus+label span {
            transform: translateY(-30px);
            font-size: 18px;
            font-weight: bold;
        }

此时的效果如下:

那为博主的功能就做到这里,但是我发现其实这里还是有一个使用的bug,就是在表单中有数据时、表单状态为blur时,label中的span会将数据遮挡:

既然问题出现在失去焦点之后,我就给input框添加了事件监听,当input框失去聚焦时,遍历所有的label,我查找了input和label中能建立连接的就是通过匹配名称(input:target.name;label:label.textContent)将两者相关联,当input框失去聚焦时,需要判断input框是否有值(value),有的话那么对应的label下的每个span都需要保持原来上浮的状态,我的办法是给每个span都添加一个active类,该类的样式与input聚焦的动画一致便可保持span不发生改变,当然如果原本span就已经添加了active那么就不需要再添加,这里就需要使用contains进行判断(这种情况会出现在input输入值之后需要修改的情况);如果input中没有值,那么就需要将active类移除,就可以保证回复原状。代码如下:

        inputs.forEach(input => {
            input.addEventListener('blur', function (e) {
                labels.forEach(label => {
                    if (e.target.name === label.textContent) {
                        label.childNodes.forEach(child => {
                            if (e.target.value) {
                                if (!child.classList.contains('active')) {
                                    child.classList.add('active')
                                }
                            } else {
                                child.classList.remove('active')
                            }
                        })
                    }
                })
            })
        })

最终效果就是文章开头的效果,对博主的想法做出了一些优化和处理

完整代码如下:

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

<head>
    <meta charset="UTF-8">
    <title>跳动的表单效果</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #c8a772;
            height: 100vh;
            margin: 0;
        }

        .form_div {
            width: 300px;
            height: 170px;
            background-color: #00000054;
            padding: 20px 40px;
        }

        .form_item {
            width: 100%;
            height: 60px;
            position: relative;
        }

        .form_item label {
            position: absolute;
            pointer-events: none;
        }

        .form_item label span {
            position: relative;
            top: 30px;
            color: #fff;
        }

        .submit {
            width: 100%;
            height: 25px;
            position: relative;
            top: 20px;
            background-color: #f2dc5e;
            border: 0;
        }

        .form_item input {
            width: 100%;
            border: none;
            background-color: transparent;
            border-bottom: 2px solid #fff;
            line-height: 30px;
            padding: 0;
            color: #fff;
            outline: none;
            position: absolute;
            bottom: 0;
        }

        .form_item label span {
            display: inline-block;
            transition: all .3s;
        }

        .form_item input:focus+label span {
            transform: translateY(-30px);
            font-size: 18px;
            font-weight: bold;
        }

        .active {
            transform: translateY(-30px);
            font-size: 18px;
            font-weight: bold;
        }

    </style>
</head>

<body>
    <div class="form_div">
        <div class="form_item">
            <input type="text" name="username" id="username" autocomplete="off">
            <label for="username">username</label>
        </div>
        <div class="form_item">
            <input type="password" name="password" id="password" autocomplete="off">
            <label for="password">password</label>
        </div>
        <button class="submit">提交</button>
    </div>
    <script>
        const labels = document.querySelectorAll('label')
        const inputs = document.querySelectorAll('input')
        labels.forEach((label) => {
            label.innerHTML = label.innerText.split('').map((letter, index) => {
                return `<span style="transition-delay:${index * 50}ms;">${letter}</span>`
            }).join('')
        })
        inputs.forEach(input => {
            input.addEventListener('blur', function (e) {
                labels.forEach(label => {
                    if (e.target.name === label.textContent) {
                        label.childNodes.forEach(child => {
                            if (e.target.value) {
                                if (!child.classList.contains('active')) {
                                    child.classList.add('active')
                                }
                            } else {
                                child.classList.remove('active')
                            }
                        })
                    }
                })
            })
        })
    </script>
</body>

</html>
posted @ 2023-03-25 13:29  超重了  阅读(45)  评论(0编辑  收藏  举报