【技巧】ionic3如何实现优雅的弹窗动画

关于这篇文章,很早前就准备写了,只是内容属于我了解的非官方资料,怕有Bug风险误导别人,又怕我表述不清楚一直没动笔,后来群里有人专门找我问这个,那我还是写一下,仅作为参考……


image.png

在了解弹窗动画前,我们先了解下CSS3中动画的基本内容:

CSS3 transition 属性

描述
transition-property 规定设置过渡效果的 CSS 属性的名称。
transition-duration 规定完成过渡效果需要多少秒或毫秒。
transition-timing-function 规定速度效果的速度曲线。
transition-delay 定义过渡效果何时开始。

其中transition-timing-function 属性规定过渡效果的速度曲线,曲线函数说明如下表所示:

描述
linear 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。
ease 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))。
ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。
ease-out 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。
ease-in-out 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))。
cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。

我比较了这几个效果,感觉没那么明显,建议点transition-timing-function此连接执行下里面实例感受下效果。


无事不登三宝殿,介绍到Transition,自然是因为ionic应用到它,而我这里主要说的是模态窗口modal。首先模态窗口内置了4个transitions:
modal-transitions.ts,截取部分代码如下:

/**
 * Animations for modals
 */
export class ModalSlideIn extends PageTransition {
  init() {
    super.init();
    const ele: HTMLElement = this.enteringView.pageRef().nativeElement;
    const backdropEle = ele.querySelector('ion-backdrop');
    const backdrop = new Animation(this.plt, backdropEle);
    const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));

    wrapper.beforeStyles({ 'opacity': 1 });
    wrapper.fromTo('translateY', '100%', '0%');

    backdrop.fromTo('opacity', 0.01, 0.4);

    this
      .element(this.enteringView.pageRef())
      .easing('cubic-bezier(0.36,0.66,0.04,1)')
      .duration(400)
      .add(backdrop)
      .add(wrapper);
  }
}

然后配置是在该源文件:modal-impl.ts,截取关键代码如下:

config.setTransition('modal-slide-in', ModalSlideIn);
config.setTransition('modal-slide-out', ModalSlideOut);
config.setTransition('modal-md-slide-in', ModalMDSlideIn);
config.setTransition('modal-md-slide-out', ModalMDSlideOut);

从上述两份源代码可以分析得出自定义PageTransition的步骤:

1、实现一个继承PageTransition的类;
2、把该类添加到配置中。

那具体怎么实现这个类?在应用中配置和源码配置有哪些差异?配置完后怎么用呢?可以看看小军此文:ionic2实战-自定义modal过渡动画

但是小军这篇文章有个风险的,它少了个基类的方法,我可以基本肯定他看了此链接:Ionic 2 Modal animations and custom animations?

我们应该点进去了解提供方法的作者:onderceylan,可以看到有意思的是,该作者回复了几个类似的问题:

image.png

再其中,包含上述链接在内,最有价值的链接是这个:
Adding custom transitions/custom modal transition,里面有人针对风险做了回复。是的,结合源码,以及常规继承的原理,我们补充调用下基类的init方法:

 super.init();

最后,基于我先前提出的几个问题,小军博客和上述链接都说明的比较清楚了,我不再说明,只是补充解析一下几个点:
1、下面部分完全是CSS3的transition内容,我们可以根据自己想要的效果来修改,其中easing里的方法个人觉得差不多,可以随便挑个用:

      .element(this.enteringView.pageRef())
      .easing('cubic-bezier(0.36,0.66,0.04,1)')
      .duration(400)

2、下面部分是CSS3的transform内容,同样可以按需修改:

wrapper.fromTo('translateY', '100%', '0%');

3、其它点,如wrapper可以调整它的透明度、宽度、高度等样式:

 wrapper.beforeStyles({ 'opacity': 1 });

4、基于上述内容,提供简单示范代码如下:

import { Animation, PageTransition } from 'ionic-angular';

/**
 * write by IT_晴天(woodstream)
 */
export class ModalFromLeftEnter extends PageTransition {
    public init() {
        super.init();
        const ele = this.enteringView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'z-index': 0, 'opacity': 0.3, 'visibility': 'visible' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateX(-100%)', 'translateX(0)');
        const contentWrapper = new Animation(this.plt, ele.querySelector('ion-content.content'));
        contentWrapper.beforeStyles({ 'width': '80%' });
        this
            .element(this.enteringView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper)
            .add(contentWrapper);
    }
}

export class ModalFromLeftLeave extends PageTransition {
    public init() {
        super.init();
        const ele = this.leavingView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'visibility': 'hidden' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateX(0)', 'translateX(-100%)');
        this
            .element(this.leavingView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper);
    }
}

export class ModalFromRightEnter extends PageTransition {
    public init() {
        super.init();
        const ele = this.enteringView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'z-index': 0, 'opacity': 0.3, 'visibility': 'visible' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateX(100%)', 'translateX(20%)');
        const contentWrapper = new Animation(this.plt, ele.querySelector('ion-content.content'));
        contentWrapper.beforeStyles({ 'width': '80%' });
        this
            .element(this.enteringView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper)
            .add(contentWrapper);
    }
}

export class ModalFromRightLeave extends PageTransition {
    public init() {
        super.init();
        const ele = this.leavingView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'visibility': 'hidden' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateX(20%)', 'translateX(100%)');
        this
            .element(this.leavingView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper);
    }
}

export class ModalFromTopEnter extends PageTransition {
    public init() {
        super.init();
        const ele = this.enteringView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'z-index': 0, 'opacity': 0.3, 'visibility': 'visible' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateY(-100%)', 'translateY(0)');
        const contentWrapper = new Animation(this.plt, ele.querySelector('ion-content.content'));
        contentWrapper.beforeStyles({ 'height': '60%' });
        this
            .element(this.enteringView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper)
            .add(contentWrapper);
    }
}

export class ModalFromTopLeave extends PageTransition {
    public init() {
        super.init();
        const ele = this.leavingView.pageRef().nativeElement;
        const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
        backdrop.beforeStyles({ 'visibility': 'hidden' });
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'translateY(0)', 'translateY(-100%)');
        this
            .element(this.leavingView.pageRef())
            .duration(300)
            .easing('ease')
            .add(backdrop)
            .add(wrapper);
    }
}

export class ModalScaleEnter extends PageTransition {
    public init() {
        super.init();
        const ele = this.enteringView.pageRef().nativeElement;
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'scale(0)', 'scale(1)');

        this
            .element(this.enteringView.pageRef())
            .duration(400)
            .easing('ease')
            .add(wrapper);
    }
}

export class ModalScaleLeave extends PageTransition {
    public init() {
        super.init();
        const ele = this.leavingView.pageRef().nativeElement;
        const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper'));
        wrapper.fromTo('transform', 'scale(1)', 'scale(0)');

        this
            .element(this.leavingView.pageRef())
            .duration(400)
            .easing('ease')
            .add(wrapper);
    }
}

最后上个效果图:


animate.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • iOS视图控制器详解 视图控制器中的视图显示在屏幕上有两种方式:最主要的方式是内嵌在容器控制器中,比如 UINav...
    coder_feng阅读 11,196评论 2 12
  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,331评论 3 44
  • 动画是CSS3中新增功能,CSS3中动画分为两种,分别是transitions和animations,transi...
    米几V阅读 638评论 0 3
  • 更好的阅读体验,请到个人博客阅读: iOS中的系统转场 请忽略标题,😂,本文记录的是对下图所示的Kind, Pre...
    CaryaLiu阅读 2,338评论 0 1
  • 从十月五号到十月十五号十天的军训生活 结束了,这十天,我们经历了许多,有欢笑,有眼泪;有欢喜,有伤心。在刚开始时...
    庆阿庆阅读 527评论 0 3