不记得什么是时候在某个网站上看到一个菜单按钮, 初始状态是三条横线, 点击打开菜单后可以变成关闭按钮, 这个过程有一个过度动画, 效果像下面这样(因为想不起来是哪个网站了, 所以下面的的gif是我根据印象实现后的效果)
这个东西实现起来挺简单, 但是这个过度动画挺有意思的;
首先是html, 外层一个div.menu
元素, 里面放三个span
用作三条杠
<div class="menu">
<span></span>
<span></span>
<span></span>
</div>
然后写点css, 设置下menu
的宽高和三条横杠, 同时也设置下过度相关的属性; 我这里用了less作为css的预处理器:
@menu-size: 50px;
@menu-stroke: 4px;
@menu-stroke-color: #fff;
@menu-animation-duration: .3s;
.menu {
display: inline-flex;
flex-direction: column;
justify-content: space-around;
height: @menu-size;
span {
@menu-size;
height: @menu-stroke;
display: block;
transition-property: opacity transform top;
transition-duration: @menu-animation-duration;
background-color: @menu-stroke-color;
}
span:nth-child(2){
transition-duration: .1s;
}
}
基本的样式写完后写开始写过度之后的样式, 给这个样式取一个名字叫menu-closeable
.menu-closeable {
position: relative;
span:nth-child(1) {
position: absolute;
transform: rotateZ(45deg);
top: @menu-size/2;
}
span:nth-child(2) {
opacity: 0;
}
span:nth-child(3) {
position: absolute;
transform: rotateZ(-45deg);
top: @menu-size/2;
}
}
这些样式写完后需要通过js来触发, 点击后在让菜单关闭状态和普通状态间进行切换
const el = document.querySelector('.menu');
let clicked = false;
el.addEventListener('click', () => {
clicked = !clicked;
if (clicked) {
el.classList.add('menu-closeable');
} else {
el.classList.remove('menu-closeable');
}
})
这样就完成了, 每次点击菜单, 菜单都会在三条横杠和一个叉叉的状态的间切换, 并且还有过度动画, 就像开头的git里面那样.
完整的演示可以参见Codepen.
Angular中的实现这个动画
模板html
<div class="menu"
[@menuCloseIcon]="{ value: isCloseIcon ? 'close' : 'menu', params: { size: size + 'px' } }"
[ngStyle]="{ height: size + 'px' }">
<span [ngStyle]="spanStyle"
[@span1Trigger]="{ value: isCloseIcon ? 'close': 'menu', params: { size: size + 'px' } }"></span>
<span [ngStyle]="spanStyle"
[@span2Trigger]="{ value: isCloseIcon ? 'close': 'menu', params: { size: size + 'px' } }"></span>
<span [ngStyle]="spanStyle"
[@span3Trigger]="{ value: isCloseIcon ? 'close': 'menu', params: { size: size + 'px' } }"></span>
</div>
ts代码
import {
animate,
animateChild,
group,
query,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, Input, OnInit } from '@angular/core';
const childrenOption = {
params: { size: '' },
};
@Component({
selector: 'cnb-moving-menu',
templateUrl: './moving-menu.component.html',
styleUrls: ['./moving-menu.component.less'],
animations: [
trigger('menuCloseIcon', [
transition('* <=> *', [
group([
query('@span1Trigger', animateChild()),
query('@span2Trigger', animateChild()),
query('@span3Trigger', animateChild()),
]),
]),
]),
trigger('span1Trigger', [
state(
'close',
style({
position: 'absolute',
transform: 'rotateZ(45deg)',
top: '{{size}}/2',
}),
childrenOption
),
state('menu', style({ transform: 'rotateZ(0)' })),
transition('* => *', [animate('.2s ease-in')]),
]),
trigger('span2Trigger', [
state(
'close',
style({
opacity: 0,
}),
childrenOption
),
state('menu', style({ opacity: 1 })),
transition('* => *', [animate('.2s ease-in')]),
]),
trigger('span3Trigger', [
state(
'close',
style({
position: 'absolute',
transform: 'rotateZ(-45deg)',
top: '{{size}}/2',
}),
childrenOption
),
state('menu', style({ transform: 'rotateZ(0)' })),
transition('menu <=> close', [animate('.2s ease-in')]),
]),
],
})
export class MovingMenuComponent implements OnInit {
@Input() size = 24;
@Input() stroke = 3;
@Input() transitionDuration = '.3s';
@Input() isCloseIcon = false;
@Input() strokeColor = '#fff';
spanStyle = {
height: this.stroke + 'px',
this.size + 'px',
'background-color': this.strokeColor,
};
constructor() {}
ngOnInit(): void {}
}
样式文件
:host {
display: inline-flex;
align-items: center;
}
.menu {
display: inline-flex;
flex-direction: column;
justify-content: space-around;
span {
@menu-size;
height: @menu-stroke;
display: block;
}
}