一、效果
二、实现代码
<!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>灵动岛</title>
<style>
:root {
--color: deeppink;
--time: 800ms;
}
body {
margin: 0;
height: 100vh;
}
.wrapper {
height: 100%;
background-size: contain;
background-repeat: no-repeat;
background-image: url(https://www.apple.com.cn/v/iphone-14-pro/a/images/overview/dynamic-island/dynamic_hw__btl4fomgspyu_large.png);
display: flex;
flex-direction: column;
align-items: center;
}
.wrapper .capsule {
display: inline-block;
width: 104px;
height: 29px;
background-color: var(--color);
border-radius: 15px;
margin-top: 28px;
position: relative;
}
.longer {
animation: longer var(--time) ease-in-out forwards;
}
/* 变长 */
@keyframes longer {
0% {
}
60% {
width: 208px;
}
80% {
transform: scaleX(1.06);
}
100% {
transform: scaleX(1);
width: 208px;
}
}
.wrapper .capsule::after {
content: '';
width: 29px;
height: 29px;
position: absolute;
right: 0;
border-radius: 50%;
background-color: var(--color);
}
.separate {
animation: separate-left var(--time) ease-in-out forwards;
}
.separate::after {
animation: separate-right var(--time) ease-in-out forwards;
}
/* 分离 */
@keyframes separate-left {
0% {
width: 208px;
}
40% {
transform: scaleX(1.06);
width: 104px;
}
100% {
transform: scaleX(1);
width: 104px;
}
}
@keyframes separate-right {
0% {
right: 0;
}
40% {
transform: scaleX(1.06);
}
100% {
transform: scaleX(1);
right: -35px;
}
}
/* 合并 */
.merge {
animation: merge-left var(--time) ease-in-out forwards;
}
.merge:after {
animation: merge-right var(--time) ease-in-out forwards;
}
@keyframes merge-left {
0% {
}
40% {
transform: scaleX(1.06);
}
100% {
transform: scaleX(1);
}
}
@keyframes merge-right {
0% {
right: -35px;
}
40% {
transform: scaleX(1.06);
}
100% {
transform: scaleX(1);
right: 29px;
}
}
/* 变大-1 */
.larger-1 {
animation: larger-1 var(--time) ease-in-out forwards;
}
@keyframes larger-1 {
0% {
}
60% {
width: 298px;
height: 44px;
border-radius: 20px;
}
80% {
transform: scaleX(1.04);
}
100% {
transform: scaleX(1);
width: 298px;
height: 44px;
border-radius: 20px;
}
}
/* 变大-2 */
.larger-2 {
animation: larger-2 var(--time) ease-in-out forwards;
}
@keyframes larger-2 {
0% {
}
60% {
width: 298px;
height: 160px;
border-radius: 40px;
}
80% {
transform: scaleX(1.04);
}
100% {
transform: scaleX(1);
width: 298px;
height: 168px;
border-radius: 40px;
}
}
</style>
</head>
<body>
<div class="wrapper">
<div class="capsule"></div>
<div class="btn-wrapper">
<button id="longerBtn">变长</button>
<button id="separateBtn">分离</button>
<button id="mergeBtn">合并</button>
<button id="larger1Btn">变大1</button>
<button id="larger2Btn">变大2</button>
<button id="reset">重置</button>
<button id="start">start</button>
</div>
</div>
<script>
const capsule = document.querySelector('.capsule')
const longerBtn = document.querySelector('#longerBtn')
const separateBtn = document.querySelector('#separateBtn')
const mergeBtn = document.querySelector('#mergeBtn')
const larger1Btn = document.querySelector('#larger1Btn')
const larger2Btn = document.querySelector('#larger2Btn')
const reset = document.querySelector('#reset')
const start = document.querySelector('#start')
longerBtn.addEventListener('click', function () {
capsule.classList.add('longer')
})
separateBtn.addEventListener('click', function () {
capsule.classList.add('separate')
})
mergeBtn.addEventListener('click', function () {
capsule.classList.add('merge')
})
larger1Btn.addEventListener('click', function () {
capsule.classList.add('larger-1')
})
larger2Btn.addEventListener('click', function () {
capsule.classList.add('larger-2')
})
reset.addEventListener('click', handleReset)
function handleReset() {
// capsule.classList.remove('longer')
// capsule.classList.remove('separate')
// capsule.classList.remove('merge')
// capsule.classList.remove('larger-1')
// capsule.classList.remove('larger-2')
capsule.classList = 'capsule'
}
const animationList = [
'longer',
'separate',
'merge',
'larger-1',
'larger-2'
]
let index = 0
let isStart = false
start.addEventListener('click', function () {
capsule.classList.add(animationList[index])
isStart = true
})
capsule.addEventListener('animationend', function (e) {
if (!isStart) return
if (['separate-right', 'merge-right'].includes(e.animationName)) {
return
}
index++
let timer = setTimeout(() => {
if (index <= animationList.length - 1) {
capsule.classList.add(animationList[index])
} else {
index = 0
isStart = false
handleReset()
timer = null
}
}, 800)
})
</script>
</body>
</html>
三、思路总结
- js可以通过事件对整个动画流程做控制,单个动画的渲染优先选择css3
- 灵动岛动画会有一种
超出边界继续放大接着又收缩的效果,在选择css3动画时优先考虑animation,animation可以使用百分比定义动画每个阶段的样式,这样我们便可以在80%时放大元素transform: scaleX(1.04),在100%时恢复大小 - 多个动画挨个执行,这里对执行动画的元素进行
animationend监听,这里可以追加下一个动画直到结束
四、transition如何做超出边界继续放大接着又收缩的效果
<style>
.item {
width: 100px;
height: 30px;
background-color: red;
border-radius: 15px;
transition: width 0.8s ease-in-out;
}
.longer {
width: 200px;
}
</style>
<body>
<div class="item"></div>
<button>重置</button>
<script>
const item = document.querySelector('.item')
const button = document.querySelector('button')
item.addEventListener('click', function () {
item.classList.add('longer')
})
button.addEventListener('click', function () {
item.classList = 'item'
})
</script>
</body>
手动获取凡尔赛曲线:cubic-bezier(0.64, -0.78, 0.39, 1.73)