Taro React组件开发(1) —— Overlay 遮罩层【渐入渐出动画遮罩层】

900 阅读3分钟

1. 遮罩层组件需求分析

  1. 常规的遮罩层,就是使用变量判断显示和隐藏;
  2. 渐入渐出动画遮罩层需要再原有遮罩层的基础上添加渐入渐出动画效果;
  3. 渐入渐出的本质就是对 opacity 的值的控制。

2. 常规遮罩层实现

2.1 代码逻辑分析

  1. 传入组件的主要参数:isOpened控制是否显示、zIndex遮罩层的层级、opacity遮罩层的透明度、position内容的显示位置;
  2. 遮罩层布局实现,第一层为遮罩层的容器、第二层为遮罩层背景显示层、第三层是遮罩层中内容的显示位置;
  3. 注意:必须在第一层和第三层添加组织冒泡事件onClick={e => {e.stopPropagation();return;}};【防止第二层点和内容的点击事件冒泡执行,导致遮罩层的异常!】
  4. 第二层绑定执行点击遮罩层,关闭遮罩层事件onHide

2.2 代码实现

2.2.1 HTML 布局实现
render () { 
    let {
      zIndex,
      opacity,
      position = "center",
      isOpened,
      children,
    } = this.props;
    let { animShow } = this.state;
    return <React.Fragment>
      {
        isOpened ?
          <View
            className="rui-overlay-component-content"
            onClick={e => {e.stopPropagation();return;}}>
            <View
              className="rui-mask-layer rui-fade-enter-active"
              onClick={this.onHide.bind(this)}
              style={`
                z-index:${ zIndex ? zIndex : 1026 };
                background-color: rgba(0, 0, 0, ${ opacity ? opacity : '0.4' });
              `}>
              <View
                className={`rui-mask-content rui-${ position }`}
                onClick={e => { e.stopPropagation(); return; }}>
                {children}
              </View>
            </View>
          </View>
          :
          <React.Fragment />
      }
    </React.Fragment>;
  }
2.2.2 JS 关闭函数实现

注意:必须判断 onClose 回调是否是函数,否则不进行操作

// 关闭遮罩层
  onHide () {
    let { onClose } = this.props;
    if (typeof onClose === 'function') {
      onClose();
    }
  }
2.2.3 SCSS 样式实现
.rui-overlay-component-content{
  .rui-mask-layer{
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.4);
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1026;
  }
  .rui-fade-enter-active{
    transition-property: opacity;
  }
  .rui-mask-content{
    position: absolute;
    z-index: 1024;
  }
  .rui-center{
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
  }
  .rui-left{
    left: 0;
    top: 0;
    height: 100%;
  }
  .rui-right{
    right:0;
    top: 0;
    height: 100%;
  }
  .rui-top{
    top: 0;
    left: 0;
    width: 100%;
  }
  .rui-bottom{
    bottom: 0;
    left: 0;
    width: 100%;
  }
}

3. 实现渐入渐出遮罩层

3.1 代码逻辑分析

  1. 在第二层添加透明度【opacity】在0-1之间进行切换;
  2. 在第二层添加透明度改变时动画执行的时间;
  3. 渐入动画和渐出动画的函数创建;
  4. 传入 isOpened 变量监听,当是true是执行渐入动画函数。

3.2 HTML 布局代码

render () { 
    let {
      opacity,
      duration,
      zIndex,
      position = "center",
      isOpened,
      children,
    } = this.props;
    let { animShow } = this.state;
    return <React.Fragment>
      {
        isOpened ?
          <View
            className="rui-overlay-component-content"
            onClick={e => {e.stopPropagation();return;}}>
            <View
              className="rui-mask-layer rui-fade-enter-active"
              onClick={this.onHide.bind(this)}
              style={`
                z-index:${ zIndex ? zIndex : 1026 };
                opacity:${animShow ? 1 : 0};
                transition-duration: ${duration ? duration : 300}ms;
                background-color: rgba(0, 0, 0, ${ opacity ? opacity : '0.4' });
              `}>
              <View
                className={`rui-mask-content rui-${ position }`}
                onClick={e => { e.stopPropagation(); return; }}>
                {children}
              </View>
            </View>
          </View>
          :
          <React.Fragment />
      }
    </React.Fragment>;
  }

3.2 JS 动画渐入渐出实现

  1. 控制动画执行的变量 animShow;
  2. UNSAFE_componentWillReceiveProps 监听传入参数 isOpened 遮罩层是否已经显示,显示立即执行渐入动画函数;
  3. 渐出动画 animHide 执行赋值 animShow 为 false;
  4. 渐入动画 animShow 需注意不能立即将 animShow 为 true, 而是使用 setTimeout 异步 200ms后执行渐入动画;
  5. 注意:如果第四步不进行 setTimeout,不会出现渐入动画效果;
  6. 关闭遮罩层函数 onHide,直接执行渐出动画函数 animHide,在函数执行完成后,再执行关闭回调函数 onClose;
  7. 注意:必须等动画执行完成,再执行回调 onClose,否则没有渐出效果。
constructor(props) {
    super(props);
    this.state = {
      animShow: false
    }
  }
  // 监听传入的 isOpened 值是 true 时,执行渐入动画
  UNSAFE_componentWillReceiveProps(nextProps){
    const { isOpened } = nextProps;
    if (isOpened) { 
      this.animShow();
    }
  }
  // 影藏遮罩层渐出动画
  animHide () {
    this.setState({
      animShow: false
    })
  }
  // 显示遮罩层渐入动画
  animShow () {
    let timer = setTimeout(() => {
      this.setState({
        animShow: true
      })
      clearTimeout(timer);
    }, 200)
  }
  // 关闭遮罩层,注意先执行渐出动画,完成后再执行关闭遮罩层的回调
  onHide () {
    let { onClose, duration } = this.props;
    if (typeof onClose === 'function') {
      this.animHide();
      let timer = setTimeout(() => {
        onClose();
        clearTimeout(timer);
      },duration ? duration : 300)
    }
  }

4. 完整代码

4.1 布局和逻辑代码

import React, { Component } from "react";
import { View } from "@tarojs/components";
import './index.scss';

class RuiOverlay extends Component {
  constructor(props) {
    super(props);
    this.state = {
      animShow: false
    }
  }
  // 监听传入的 isOpened 值是 true 时,执行渐入动画
  UNSAFE_componentWillReceiveProps(nextProps){
    const { isOpened } = nextProps;
    if (isOpened) { 
      this.animShow();
    }
  }
  // 影藏遮罩层渐出动画
  animHide () {
    this.setState({
      animShow: false
    })
  }
  // 显示遮罩层渐入动画
  animShow () {
    let timer = setTimeout(() => {
      this.setState({
        animShow: true
      })
      clearTimeout(timer);
    }, 200)
  }
  // 关闭遮罩层,注意先执行渐出动画,完成后再执行关闭遮罩层的回调
  onHide () {
    let { onClose, duration } = this.props;
    if (typeof onClose === 'function') {
      this.animHide();
      let timer = setTimeout(() => {
        onClose();
        clearTimeout(timer);
      },duration ? duration : 300)
    }
  }
  render () { 
    let {
      opacity,
      duration,
      zIndex,
      position = "center",
      isOpened,
      children,
    } = this.props;
    let { animShow } = this.state;
    return <React.Fragment>
      {
        isOpened ?
          <View
            className="rui-overlay-component-content"
            onClick={e => {e.stopPropagation();return;}}>
            <View
              className="rui-mask-layer rui-fade-enter-active"
              onClick={this.onHide.bind(this)}
              style={`
                z-index:${ zIndex ? zIndex : 1026 };
                opacity:${animShow ? 1 : 0};
                transition-duration: ${duration ? duration : 300}ms;
                background-color: rgba(0, 0, 0, ${ opacity ? opacity : '0.4' });
              `}>
              <View
                className={`rui-mask-content rui-${ position }`}
                onClick={e => { e.stopPropagation(); return; }}>
                {children}
              </View>
            </View>
          </View>
          :
          <React.Fragment />
      }
    </React.Fragment>;
  }
}

export default RuiOverlay;

4.2 样式代码

.rui-overlay-component-content{
  .rui-mask-layer{
    transition-duration: 300ms;
    opacity: 1;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.4);
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1026;
  }
  .rui-fade-enter-active{
    transition-property: opacity;
  }
  .rui-mask-content{
    position: absolute;
    z-index: 1024;
  }
  .rui-center{
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
  }
  .rui-left{
    left: 0;
    top: 0;
    height: 100%;
  }
  .rui-right{
    right:0;
    top: 0;
    height: 100%;
  }
  .rui-top{
    top: 0;
    left: 0;
    width: 100%;
  }
  .rui-bottom{
    bottom: 0;
    left: 0;
    width: 100%;
  }
}

5. 总结

  1. 每个动画执行的时间差,如果没有对应的时差,可能会导致动画不执行或者动画没有执行完成就关闭等情况;
  2. 第一层和第三层必须添加阻止冒泡,否则在组件使用的时候,会由于冒泡导致各种点击事件问题。