表单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>