blog icon indicating copy to clipboard operation
blog copied to clipboard

带突出三角形的下拉对话框动画实现

Open ryancui92 opened this issue 7 years ago • 0 comments

在很多时候需要实现一个弹出框,效果类似这样:

需求

要留意的是这个对话框有一个指示弹出位置的三角,并且需要实现下拉动画和上滑动画。

一个 box 加 :before

针对需求,有初步的方案

  • 指示三角:在 box 前加伪类 :before、三角形通过边框法实现 border-bottom
  • 动画:transition height 由于 box 高度可以固定,直接使用 height 做动画

关于边框法实现三角形可以自行 Google,原理很简单,就是把 box model 的内容去掉 width: 0; height: 0,利用四个 border 都是三角形设置三条边为透明即可。

这里要注意的是三角形的 top 应该是两倍的上下边框的 border-width

这里我们发现这个三角形没有出现,原因是我们在 box 上加了 overflow: hidden,这是为了让 box 的 height 变成 0 时,box 的内容不会溢出到 box 外(不然这个下拉动画就没用了)。由于要使用 transition height 就必须加上这个 overflow: hidden,那么所有要在 box 的 HTML 内部实现三角形的方法都不会 work,因为这个三角形实际上一定会 overflow 到 box 外。

分成两个 box

既然不能在 box 内部放这个三角形,那就放到外面呗,在 box 的同层加一层 div,额外放这个三角形。

具体实现时,把这个 before-box 的背景设成透明就 ok 了。

两个 box 后的 transition

分成两部分之后面临的第二个问题是,动画怎么办?当我还是一个 box 的时候,设置了 transition,js 要做的事就很简单:

// 下拉
ele.style.height = '200px'

// 收起
ele.style.height = '0px'

但分成两部分之后,如果简单得认为只需要在 before-box 上也设一个 transition height 然后这样:

// 下拉
before.style.height = '20px'
ele.style.height = '200px'

// 收起
before.style.height = '0px'
ele.style.height = '0px'

那就 TOO NAIVE 了,这时候你会发现这两个 div 是同步进行动画的,给人一种强烈的违和感。

既然我想有先后顺序,第一个想到的应该是 transition-delay,加个延迟就好了啊:

.before-box {
  transition: height 0.05s ease-out;
}

.box {
  transition: height 0.3s 0.05s ease-out;
}

下拉的时候很完美啊,但收起的时候就悲剧了,before-box 先收起,然后才是 box 的动画,违和感更强了。

这时我们知道,其实我们需要一种带方向性的 transition 机制,从 0 到 100px 和从 100px 到 0 我们希望能够 apply 不同的 transition 参数(这里是不同的 delay)。

于是找了一下,貌似 transition 并不支持这种方向性的机制。但是没关系,我们可以通过 js 自行创造,用不同的 class 来预定义不同的 transition 参数,然后在实际动画前变更这些 class 就可以实现带方向的 transition 了。

.triangle-show {
  transition: height 0.05s linear;
}

.triangle-hide {
  transition: height 0.05s 0.35s linear;
}

.box-show {
  transition: height 0.3s 0.05s ease-out;
}

.box-hide {
  transition: height 0.3s ease-out;
}

在 show/hide 之前切换 class

// 下拉
before.classList.add('triangle-show')
box.classList.add('box-show')
before.classList.remove('triangle-hide')
box.classList.remove('box-hide')
...

// 收起
before.classList.add('triangle-hide')
box.classList.add('box-hide')
before.classList.remove('triangle-show')
box.classList.remove('box-show')

当然如果不想定义这么多的 class 也可以直接选择在 js 中更改 transition 的属性 ele.style.transitionDelay = '0.35s'

ryancui92 avatar Jul 16 '17 11:07 ryancui92