「antd源码分析」Wave——元素点击扩散效果

「antd源码分析」Wave——元素点击扩散效果

Wave组件为antd中许多元素提供了点击扩散特效的支持,例如Button、Checkbox和Radio等。

Wave on Button

Wave on Checkbox
Wave on Radio

特点

  • Wave本身是一个组件,将需要实现点击特效的元素通过props.children传入。
  • 无侵入,使用伪元素执行特效,不会破坏原有的Dom层次结构。
  • 扩散特效颜色跟随被点击元素边框自动改变

主要实现

1. bindAnimationEvent

componentDidMount后会首先调用该方法。

1
2
3
4
5
6
7
8
9
10
11
12
// dom点击的时候触发该方法
const onClick = (e: MouseEvent) => {
// ...
};
// 给dom添加点击事件
node.addEventListener('click', onClick, true);
// instance 有cancel方法,用于Unmount取消事件绑定
return {
cancel: () => {
node.removeEventListener('click', onClick, true);
},
};

componentDidMount中通过findDOMNode获取到children所对应的DOM,将函数内定义的onClick(不同于Wave实例的onClick方法)绑定为node点击事件,最后返回一个包含cancel方法的对象,供componenWillUnmount移除事件绑定。

2. node.onClick

Wave会存在两个onClick方法, 一个是实例的onClick方法,另一个是实际绑定在node上的事件方法,也就是这个方法。

1
2
3
4
5
6
7
8
9
// 恢复状态
this.resetEffect(node);
// 根据边框颜色确定 wave 颜色
const waveColor =
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
getComputedStyle(node).getPropertyValue('border-color') ||
getComputedStyle(node).getPropertyValue('background-color');
// 下一次runloop执行this.onClick 触发动画
this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);

  1. 当node点击后,会将状态清空一次,以防特效动画未结束二次触发。
  2. 其次会获取node的边框颜色,为扩散特效提供支持。
  3. 最后会在下一个runloop执行实例的onClick方法,真正开始执行特效代码。

3. 执行特效的onClick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const attributeName = 'ant-click-animating-without-extra-node'
node.setAttribute(attributeName, 'true');
styleForPesudo = styleForPesudo || document.createElement('style');
// 如果外边框的颜色不是灰色 白色 就是用默认的primary-color
// 否则动态添加style 指明border-color
if (
waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
this.isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent'
) {
// Add nonce if CSP exist
if (this.csp && this.csp.nonce) {
styleForPesudo.nonce = this.csp.nonce;
}
// 动态创建style标签 通过css给 ::after添加边框颜色
extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML = `[ant-click-animating-without-extra-node="true"]:after { border-color: ${waveColor}; }`;
if (!document.body.contains(styleForPesudo)) {
document.body.appendChild(styleForPesudo);
}
}
// 动画结束后进行resetEffect
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);

其实wave的特效主要是用css来实现的,onClick方法主要做了三件事:

  1. 为node添加attribute,触发node伪元素的css animation来执行特效。
  2. 动态向body添加style,设置node伪元素border-color来实现动态切换颜色
  3. 借助css-animation库来监听css-animation结束清除attribute

onClick设置颜色前会检查当前元素的颜色是否为灰色、白色或者透明,如果满足条件则不会添加style设置,而是直接采用css设定好的primary-color

4. CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[ant-click-animating-without-extra-node='true'] {
position: relative;
}

[ant-click-animating-without-extra-node='true']::after {
position: absolute;
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
display: block;
border: 0 solid @primary-color;
border-radius: inherit;
opacity: 0.2;
animation: fadeEffect 2s @ease-out-circ, waveEffect 0.4s @ease-out-circ;
animation-fill-mode: forwards;
pointer-events: none;
content: '';
}

@keyframes waveEffect {
100% {
top: -@wave-animation-width;
right: -@wave-animation-width;
bottom: -@wave-animation-width;
left: -@wave-animation-width;
border-width: @wave-animation-width;
}
}

@keyframes fadeEffect {
100% {
opacity: 0;
}
}

最后

Wave组件的精髓在于没有破坏原有Dom结构的情况下,使用CSS伪元素的方法实现特效。这种方式对于我们在编写组件或开源库的时候有借鉴意义。

另外如果我们要将Wave组件用到自己项目的时候,需要注意目前antd的wave只能同时显示一种特效颜色,也就是如果你特效时间设定的比较长,用户有机会让多个特效同时出现的时候,这些特效的颜色永远会显示最后一次特效的颜色。原因是因为动态添加的style标签中的attribute-name都是设定的同一个。可以考虑为attribute-name加入id来解决这个问题。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×