精细控制transition细节 - 实现一个活泼的弹框__Vue.js
发布于 3 年前 作者 banyungong 1291 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

作为一个前端,用户的良好体验是我们的基本信仰!一个有趣的交互会使用户对页面的好感提升,一个恰当的反馈能使用户轻松理解你想表达的意思。

本篇文章带大家一步一步打造一个活泼的轻量级弹框。你会学到:

  • transition细节控制
  • 如何添加css3小动画
  • 如何通过props实现可替换组件

HTML结构

<div class="dialog__wrap">
	<div class="dialog__overlay"></div>
	<div class="dialog">
		<div class="dialog__header"></div>
    	<div class="dialog__content"></div>
		<div class="dialog__footer"></div>
	</div>
</div>
HTML结果

我们为.dialog__wrap.dialog__overlay都加上

position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;

其实这两个元素是叠在一起的,为什么要单独用一个div来做遮罩呢,是因为我们想要独立控制遮罩层的过渡,并且不影响弹框的过渡。 然后.dialog居中布局就好了。

显示/消失过渡

首先来看最基础的,没有过渡。 我们使用最简单的v-if来实现效果。

<div v-if="visible" class="dialog__wrap"> ... </div>
export default {
  data() {
    return {
      visible: false
    };
  }
}

然后我们只需要控制visible的值可以了。

现在我们来给它加一点过渡效果,你应该知道我们要用什么来实现,就是transition组件。

<transition name="vivid" :duration="600">
  	<div v-if="visible" class="dialog__wrap">
		<div class="dialog">
    		<div class="dialog__header"></div>
    		<div class="dialog__content"></div>
			<div class="dialog__footer"></div>
		</div>
 	</div>
</transition>

transition根据过渡的状态提供了6个类名供我们使用: -enter, -enter-to, -leave, -leave-to, -leave-active, -enter-active

在我们的过渡中, -enter-to-leave就是最终的状态,所以我们用不到这俩。

接下来我们详细介绍剩下的四个类。

细节把控

如果你对transition的类名生命周期不太熟的话,可以先回顾官方文档

接下来先看一下我们想要实现的效果。

vivid-dialog-example.gif

GIF的帧率不是很高,可能看不清细节,大体上来说就是:

  1. 出现/消失效果为缩放
  2. 弹出框容器,标题,内容,底部按钮错开缩放
  3. 缩放带有弹跳效果 (bounce)
  4. 显示和消失的过渡是镜像的而不是相同

首先我们来实现简单的缩放效果

.vivid-enter,
.vivid-leave-to {
  .vivid-dialog {
    opacity: 0;
    transform: scale(0.5);
  }
  .vivid-dialog__header,
  .vivid-dialog__content,
  .vivid-dialog__footer > *:first-child,
  .vivid-dialog__footer > *:nth-child(2) {
    opacity: 0;
    transform: scale(0.6);
  }
}

这样所有元素都会同时的进行变大/变小。但这不是我们想要的,我们想要的是像水泡一样的一个一个冒出来的效果。 这里关键是每个元素的出场时间不同,也就是延时不同。

这里依助于-enter-active-leave-active,我们可以定义过渡中的时间延时。

.vivid-enter-active {
  .vivid-dialog__header {
    transition-delay: 0.05s;
  }
  .vivid-dialog__content {
    transition-delay: 0.1s;
  }
  .vivid-dialog__footer > *:first-child {
    transition-delay: 0.2s;
  }
  .vivid-dialog__footer > *:nth-child(2) {
    transition-delay: 0.25s;
  }
}

这段scss能够让弹框标题,内容和底部按钮依次冒出来。但是它看起来还不够调皮,所以我们为它增加弹跳的效果。 那么问题来了,弹跳效果怎么添加呢?其实也很简单,就是贝塞尔曲线。

当我们给transition设置了transition-timing-function时,将时间作为x,那么元素的属性值就会被映射为y值。我们常见的easeease-inease-out等等的y轴比例都没有超过1,也就是说过渡过程中不会出现比如width大于最终值这种效果。但是实际它是可以比1大的,比如:

截屏20210125 下午5.50.29.png

像这样的曲线,过渡就会有个回弹的效果,也就是属性值*1.x,最后又变回属性值*1

那用到我们这就是:

vivid-enter-active {
  .vivid-dialog {
    transition: all 0.4s cubic-bezier(0.44, 0.03, 0.33, 1.76);
  }
  .vivid-dialog__header,
  .vivid-dialog__content,
  .vivid-dialog__footer > *:first-child,
  .vivid-dialog__footer > *:nth-child(2) {
    transition: all 0.4s cubic-bezier(0.44, 0.03, 0.33, 1.6);
  }
}

到这里,开始的过渡效果已经OK了,但是!不能将开始的效果照搬放到-leave-active中去。因为我们想实现的效果是,离开的时候最后出现的元素先消失,并且弹跳的效果出现在过渡一开始而不是最后,也就是说transition-timing-function完全不一样。

哦还有一点,记得由于我们的过渡时长是由所有元素决定的,而transition自动识别的是根元素的过渡结束事件,所以我们需要显示指定:duration="600"

大家可以先自己想想怎么改动,源码放在最下方。 到这里,我们已经有一个活泼的弹框了,下面是优化以及增加组件扩展性的内容,可以选择跳过。

拒绝关闭动画

点击弹框之外的区域关闭弹框已经成为了一个约定俗成的交互方式,有时候我们不希望用户点太快直接关闭弹框,我们需要他了解弹框的内容。 所以我们这里做了一个拒绝关闭的动画(当然是可以配置的),这个效果在上面也演示过了,我们讲一下原理和细节。

首先原理很简单,在用户点击时增加动画的class,然后在动画结束后删除class

首先我们有一个动画类

@keyframes headShake {
  0% {
    transform: translateX(0);
  }
  6.5% {
    transform: translateX(-6px) rotateY(-9deg);
  }
  18.5% {
    transform: translateX(5px) rotateY(7deg);
  }
  31.5% {
    transform: translateX(-3px) rotateY(-5deg);
  }
  43.5% {
    transform: translateX(2px) rotateY(3deg);
  }
  50% {
    transform: translateX(0);
  }
}

.headShake {
  animation-timing-function: ease-in-out;
  animation-name: headShake;
  animation-duration: 0.6s;
  animation-fill-mode: both;
}

只要一个元素添加了headShake类名,就会有一个摇头的动画,更多动画可以参考animate.css

接下来就是如何操作类名了:

onClickOverlay() {
  this.closeOnClickOverlay && this.close();

  if (!this.closeOnClickOverlay && this.visible) {
    const el = this.$refs.dialog;
    el.classList.add("headShake");
    el.addEventListener(
      "animationend",
      () => {
        el && el.classList.remove("headShake");
      },
      { once: true }
    );
  }
},

这个函数在点击遮罩时执行,先判断是否允许关闭,再判断是否已关闭(但是在动画中DOM还没消失),最后添加headShake。 并且监听了animationend事件,动画结束后就会自动删除该类名,以便下次还能触发。

这里值得注意的一点是,transition也是监听的animationend/transitionend事件,所以在摇头的过程中关闭弹框就会有些难看。我们得在关闭之前取消掉摇头动画:

clearAnimation() {
  this.$refs.dialog &&
    this.$refs.dialog.getAnimations().forEach((animation) => {
      animation.cancel();
    });
},

再试一下,随便怎么你啥时候关,没问题!

可定制按钮

大多数弹框组件还有☝️问题,他们的内置按钮组件太丑了!还不能换。所以我们来解决一下这个问题,我们为普通用户提供一个默认按钮(很普通),为想统一UI风格的小伙伴提供一种替换方法。

这里是借鉴类似接口的思想,通过vueprops属性和component组件,我们可以制定一个抽象组件:

<component
  v-if="footer.ok"
  primary
  :loading="okLoading"
  :disabled="okLoading ? 'disabled' : undefined"
  v-bind:is="btnComponent"
  @click="handleOK"
  >确定</component>
.vivid-dialog-btn {
  height: 40px;
  width: 100%;
  display: inline-block;
}

也就是说,只要组件正确处理了primary,loading,disabled,click,default slot,并且有vivid-dialog-btn类。 那么,就可以正确嵌入到弹框中,并且可以有预期的样式和行为。除此之外,组件可以拥有自己的任何行为和样式。

我称其为抽象组件哈哈[二哈]。

截屏20210125 下午6.28.57.png

提供一个自己的按钮。

总结

  • transition可以实现细节控制
  • 通过animationend实现多次触发动画
  • 抽象法组件使组件变得更易扩展

实际上呢,本篇文章是作者编写插件的一个小结,源码和文档可与DakerHub查看。可以直接使用npm安装使用。也欢迎大家的想法和PR!

历史精选

  1. 如何在10分钟之内完成一个业务页面 - Vue的封装艺术
  2. 新手也能看懂的虚拟滚动实现方法
  3. Vue3有哪些不向下兼容的改变
  4. Axios源码分析

原文-我的小破站

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 格砸 原文链接:https://juejin.im/post/6921644175200878606

回到顶部