关于动画:C4D在影视动画制作中的局限性

不愤不启不悱不发,学,而后知有余。大家都晓得,C4D只是整个影视动画制作流程当中的一环,残缺的流程应该蕴含上面4个阶段:后期筹备--故事脚本-三维制作-后期制作。理解了流程后,咱们要晓得,从故事创意到最终的动画实现,须要用到的软件大略有6个:1)pr剪辑软件:能够对视频进行采集、剪辑、调色、丑化音频、字幕增加、输入、DVD刻录等一整套流程,视频在剪辑输入后就是成片了。2)AE特效:能够创立无数种引人注目的动静图形和震撼人心的视觉效果,也就是俗话说的特效,在视频特效阶段有着重要作用。3)AU音效:能够对音效进行音频混合、编辑、管制和成果解决,一个好的音效能够让视频本性难移,在视频剪辑的阶段,须要搭配音乐合成后才可成片输入。4)PS设计:能够进行图像编辑和设计,在影视动画制作中,对ps要求不高,个别用到的不多。5)ME编码:能够对视频和音频进行编码,设置各种格局的视频,常见的视频格式有MPEG、AVI、nAVI、ASF、MOV、3GP、WMV、DivX、XviD、RM、RMVB、FLV/F4V等,在剪辑实现后输入时须要用到。6)C4D:能够进行模型创立、三维动画渲染和制作。 所以从软件角度上来看,软件都是从性能上确定本人的边界,C4D只是影视动画制作过程的其中一个环节,想要本人可能做出视频成片,还须要把握其他软件的应用。有些同学可能会有这样的疑难:我把握了C4D曾经能拿到不错的薪资了,为啥还要学其他软件呢,我做好公司的一个螺丝钉不就行了?我的确对本人要求比拟高,想学习更多的常识,然而学完这些软件对我的益处是什么呢?这里给大家分析下现实情况,个别能够熟练掌握这6个软件技能的退职场上的职位叫做影视前期师,或者视频制作师。这个岗位的均匀薪资是13K,相对来说比C4D的均匀薪资是要高2k的,咱们能够看下一些招聘网站上的数据:影视动画方向-某招聘网站截图某招聘网站截图而且从发展前景来看,全能的设计师是比只把握C4D的设计师路子要宽的多的。我举个例子,咱们要做一个菜,有买菜的,有洗菜的,有切菜的,还有炒菜的,这时候如果一个只会切菜,一个却能够轻松把握全流程,那谁能拿更高工资,谁能更快降职,也就可想而知了。而且在待业时,全能设计师可抉择的公司也更多更高大上,更重要的是,能够本人做自媒体守业,当初是短视频时代红利期,本人全职或者兼职做短视频也是不错的抉择哦。

September 26, 2023 · 1 min · jiezi

关于动画:实现网易新闻的图中图PIP二零一七年娱乐圈画传

netease-news实现网易新闻的图中图(PIP)《二零一七年娱乐圈画传》查看源码 点击或者扫码预览: 绘画过程剖析下面的动图,如何实现?直观上感觉像是一个大图,但认真想想必定不对,那得多大的图~看了下网页构造,用了一个 canvas 来显示,应该就是把图像一帧一帧的画到 canvas 下面,比方 1 秒画 60 帧,就能达到顺滑的膨胀成果。 绘制的时候,同时存在两张图,最后的时候图 1 和屏幕重合,图 2 在屏幕外边。随着工夫的推移,图 2 和图 1 一起缓缓放大,最终图 2 和屏幕重合,图 1 膨胀到了图 2 的 PIP 地位。这时候去掉图 1,增加图 3,图 3 和图 2 一起膨胀,而后再增加图 4,始终到最初一张图。 想要实现整个过程,须要保障: 所有图、屏幕显示区域(宽度最大,长度可能有空白或者滚动条)、以及 PIP 的长宽比是统一的,这样能力在放大后完满重合以后的图片,须要晓得下一张图的 PIP 的地位和大小,这样能力放大挪动到指定地位个别这种画中画都是有事件程序的,所以要求图片的程序是排列好的,依照程序膨胀绘画须要用到这个CanvasRenderingContext2D.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)函数,函数的参数比拟多,这里列出来: 参数Descriptionimage绘制到 canvas 的元素,能够是图片、视频和 canvassximage 的矩形(裁剪)抉择框的左上角 X 轴坐标syimage 的矩形(裁剪)抉择框的左上角 Y 轴坐标sWidthimage 的矩形(裁剪)抉择框的宽度sHeightimage 的矩形(裁剪)抉择框的高度dximage 的左上角在指标画布上 X 轴坐标dyimage 的左上角在指标画布上 Y 轴坐标dWidthimage 在指标画布上绘制的宽度dHeightimage 在指标画布上绘制的高度第一个参数,没啥说的,就是想要绘制的指标元素,咱们这里是图片 ...

March 22, 2023 · 2 min · jiezi

关于动画:TIL-学习骨骼动画

理解了下骨骼动画的场景和常见工具,走通 H5 中实现骨骼动画的能力。 成绩 demo: 常用工具dragonbones 收费spine 免费blender+doatools 收费综合我的场景和费用和易用性,抉择 dragonbones 进一步理解 demo要学习 Dragonbones 根本用法,倡议间接看视频教程 https://www.bilibili.com/vide... 为了实现下面 gif demo 成果,我大略做了上面这些事: 理解 Dragonbones 界面:https://docs.egret.com/dragon...理解骨骼动画基本概念:https://docs.egret.com/dragon...学习网格:https://docs.egret.com/dragon...学习蒙皮权重:https://docs.egret.com/dragon...查看部份视频教程:https://www.bilibili.com/vide...转到”骨骼拆卸“模式:https://docs.egret.com/dragon...导入图片为图片创立网格 举荐用边线工具创立网格,十分不便(可选)进一步细调创立骨架 (可选)进一步细调主动调配权重 (可选)手动细调转到“动画制作”模式制作动画 举荐关上主动关键帧性能(疑难)没有如文档中所说呈现缩放骨骼的管制柄,只能通过底部输入框调整数值 能够在浏览器中预览导出,并在 cocos creator 中应用

February 16, 2023 · 1 min · jiezi

关于动画:用-SpriteJS-来开发高性能的-web-图形和动画系统

一款跨终端 canvas 绘图框架,canvas 做动画素来没有这么简略过。 SpriteJS 介绍SpriteJS 是跨平台的高性能图形动画零碎,通过封装好的简略办法实现在 web、node、桌面利用和小程序上的图形绘制和各种晦涩的动画成果。由 360 公司的前端团队 360 奇舞团开发以及保护。 最新的 SpriteJS Next 是 SpriteJS 的新版本,在浏览器端反对 webgl2 渲染,并可向后兼容降级为 webgl 和 canvas2d。 SpriteJS 技术个性操作简略,像操作DOM对象一样操作画布上的图形元素基于 canvas 绘制的文档对象模型,也反对高性能的 WebGL2 渲染反对多图层解决图形、文本、图像渲染四种根本精灵类型:Sprite、Path、Label、Group反对根底和高级的精灵属性,精灵盒模型、属性与 CSS3 具备高度一致性。简便而弱小的 Transition、Animation API反对雪碧图和资源预加载可扩大的事件机制高性能的缓存策略对 D3、Matter-js、Proton和 其余第三方库敌对跨平台,反对 node-canvas 、微信小程序反对在 Vue.js 中应用以及服务端渲染SpriteJS 能用来做什么?相熟 canvas 的开发者应该晓得,应用 canvas 语法去画图形和做动画十分繁琐,SpriteJS 最突出的特点就是通过封装简化了动画成果的开发难度,不仅节俭了代码量,也让代码更清晰易保护,同时实现的动画成果也十分好。 作为一款图形绘制以及动画零碎,SpriteJS 能实现很多离奇的成果,被使用在各种交互丰盛的动静图表、营销流动、小游戏场景中,甚至能够独自用来做游戏。 SpriteJS 反对在 Vue(衍生版为 SpriteVue)和微信小程序中应用,那些 CSS 难以实现的成果,试试用 SpriteJS 来实现吧。 SpriteJS 还反对在 node 服务端渲染,此类的利用场景个别是将绘制好的图形保留成 png 图片,或者将动画保留成 gif 输入给前端。 上手开发体验感触SpriteJS 官网的文档写得很清晰,且配有大量所见即所得的代码范例,如果有应用专门的 H5 游戏框架的教训,上手齐全没难度,而且我感觉 SpriteJS 比游戏框架了解起来要更简略。 ...

April 29, 2022 · 1 min · jiezi

关于动画:看动画学算法之二叉搜索树BST

简介树是相似于链表的数据结构,和链表的线性构造不同的是,树是具备层次结构的非线性的数据结构。 树是由很多个节点组成的,每个节点能够指向很多个节点。 如果一个树中的每个节点都只有0,1,2个子节点的话,这颗树就被称为二叉树,如果咱们对二叉树进行肯定的排序。 比方,对于二叉树中的每个节点,如果左子树节点的元素都小于根节点,而右子树的节点的元素都大于根节点,那么这样的树被叫做二叉搜寻树(Binary Search Tree)简称BST。 明天咱们来探讨一下BST的性质和对BST的基本操作。 BST的根本性质刚刚咱们曾经讲过BST的基本特征了,当初咱们再来总结一下: BST中任意节点的左子树肯定要比该节点的值要小BST中任意节点的右子树肯定要比该节点的值要大BST中任意节点的左右子树肯定要是一个BST。看一张图直观的感受一下BST: BST的构建怎么用代码来构建一个BST呢? 首先,BST是由一个一个的节点Node组成的,Node节点除了保留节点的数据之外,还须要指向左右两个子节点,这样咱们的BST齐全能够由Node连贯而成。 另外咱们还须要一个root节点来示意BST的根节点。 相应的代码如下: public class BinarySearchTree { //根节点 Node root; class Node { int data; Node left; Node right; public Node(int data) { this.data = data; left = right = null; } }}BST的搜寻先看下BST的搜寻,如果是下面的BST,咱们想搜寻32这个节点应该是什么样的步骤呢? 先上图: 搜寻的根本步骤是: 从根节点41登程,比拟根节点和搜寻值的大小如果搜寻值小于节点值,那么递归搜寻左侧树如果搜寻值大于节点值,那么递归搜寻右侧树如果节点匹配,则间接返回即可。相应的java代码如下: //搜寻办法,默认从根节点搜寻 public Node search(int data){ return search(root,data); } //递归搜寻节点 private Node search(Node node, int data) { // 如果节点匹配,则返回节点 if (node==null || node.data==data) return node; // 节点数据大于要搜寻的数据,则持续搜寻右边节点 if (node.data > data) return search(node.left, data); // 如果节点数据小于要搜素的数据,则持续搜寻左边节点 return search(node.right, data); }BST的插入搜寻讲完了,咱们再讲BST的插入。 ...

December 8, 2021 · 2 min · jiezi

关于渲染:瑞云渲染请你看西游记之再世妖王

“西游记”能够说是最被国人熟知的IP之一了,以“孙悟空”为配角的影视作品、动画作品也是层出不穷。 但这样的孙悟空,你肯定没见过—— 烟熏妆、大花臂、暗黑气质。这样一个颠覆传统形象的孙大圣,出自将于4月2日上映的动画电影《西游记之再世妖王》。 这部令人期待的动画电影由瑞云渲染提供云渲染服务。国风的动画,颠覆的改编,暗黑系西游行将全新开启!让咱们一起来看看这次新版西游会有什么样的魅力吧。 ????文末还有送票福利哦!???? 《西游记之再世妖王》导演: 王云飞上映日期: 2021-04-02剧情:混沌初开之时,世上的第一只妖降世,名元蒂,被奉为妖祖。千百万年后,今日妖王孙悟空被唐僧从五行山下救出,悟空许可爱护唐僧,返回西天取经。而传说中的妖祖元蒂再次现世,三界奄奄一息。两代妖王注定终极一战,但这次,孙悟空遇上了真正的劲敌。西游史上“最强反派”,万妖来袭精心制作历时六年之久,《西游记之再世妖王》率领咱们走进“万妖来袭”的魔幻世界。从预告片中咱们能够看到“万妖之祖”元蒂复原本体重现世间,群妖集结蠢蠢欲动,暗黑时刻正式开启。 一众西游经典妖怪形象也在预告片中一一曝光,九头虫、多目鬼王、九尾狐、黄狮精,每个妖怪的形象都霸气侧漏、战斗力满格。与光明系的孙悟空掀起三界激战,谁将成为“世界上最厉害的妖”,令人期待。 触目惊心的暗黑西游世界为了可能打造一个让观众感觉真实可信的西游世界,主创团队通过上千次的批改,披星戴月对设定的斟酌与斟酌,最终为咱们呈现出了这个全新的暗黑系西游世界。 影片中呈现大量通过历史讲究的西游元素,比方概念图中呈现的庙宇楼阁,都是对实在文物进行款式提取参考,严格依照唐朝修建、用具等元素进行绘图和建模工作,以体现恢弘的“盛唐气象”。烈火蔓延的火焰山、残破的山神庙、冷峻的千年峰,这些场景都以全新的美术格调呈现出西方经典神话的独特魅力。 《西游记之再世妖王》场景概念图 ????送票福利???? 《西游记之再世妖王》将于4月2日上映,瑞云请你收费看 关注“Renderbus瑞云渲染”B站账号,点赞视频并在视频下留言写出你对《西游记之再世妖王》的期待,咱们将在3月31日随机抽取10位侥幸鹅,每人取得2张50元电影! 西游最强一战,刮目相待! 4月2日,《西游记之再世妖王》等你来看!

March 12, 2021 · 1 min · jiezi

关于动画:渲染教程用blender和Zbrush创作一只巨蟹怪兽

BY: ABBY CRAWFORD  嗨,大家好,我是来自墨西哥奥里萨巴(Orizaba)的Javi。当初我还是一名软件工程业余的学生,然而我对电影业充满热情,并心愿成为电影行业从业者的一部分。 我在2019年7月首次接触3D行业,但在2020年,我决定开始认真的学习3D行业并开始承受一些在线课程。我始终在应用Crehana和Domestika,但次要是在网络上,在那里我发现了很多很棒的内容,这些简直教会了我所有常识。 灵感在开始创立一个作品时,我决定先尝试将2D作品转换为3D作品。我分割了一位很棒的艺术家,并失去了他的许可将他的2D概念艺术作品创立为3D格局。我十分的喜爱他的作品,这个我的项目看起来十分的酷,而且是我目前的作品中最喜爱的一个。 我很喜爱Instagram,不论是作品还是作者都十分的酷,它下面的很多优秀作品给了我很多灵感。特地是能够进间隔接触到那些在一流工作室工作的人,真的是很棒!这就是为什么我喜爱这个行业。 工具类通常状况下,我习惯应用的软件是ZBrush和BlenderRenderbus瑞云渲染反对ZBrush和Blender插件)。尽管所有的工作步骤都能够在Blender中实现,然而我还是喜爱应用ZBrush雕刻模型和制作纹理,而后放入Blender中进行外观的开发,照明和渲染。有时候想要制作一些非凡的成果时,我还会应用Substance Painter制作纹理。 工作流程ZBrush 在开始制作我的项目时的第一步是寻找大量参考,如果主题是其余艺术家创作的作品,我会先在网络上宣召是否有人曾经将其3D化,或者也能够间接参考他人的雕刻作品。 雕刻是我最喜爱,也是最难的步骤之一。当我开始雕刻时,我会参考所有可用的材料。例如,如果我要雕刻恐龙,我会查看每种动物的解剖构造,以理解肌肉的外观以及它们的静止形式。 这是我的项目中最重要的局部,如果模型的解剖构造谬误,即便不做动画,它看起来也还是会很奇怪。因而,这部分的测试破费了我大量的工夫。 而后是纹理制作,这也是最乏味的局部,制作时能够在其中看到色彩和模型的最终外观。我先应用污垢纹理作为根底色彩,而后应用其余一些污垢纹理色调来加强身材的各个部位。最初,应用遮罩使细节变得亮堂,并筹备好其余的所有货色。 Blender 当模型和纹理阶段实现之后,我开始应用Blender 2.81制作其余的局部。首先,我导入每个OBJ文件并给予位移贴图。 接下来,我开始在EEVEE渲染器中调整灯光。通常我会先在模型旁边设置区域灯光用来突出显示形态,而后开始应用其余辅助灯光,来让其余的局部可能显示出细节。除此之外,还减少了一个强度非常低的HDRI。 接下是外观开发,对于我来说,在Blender中绘制贴图是很艰难的事件,如果找不到好的纹理,那么后果会很蹩脚。而且,比照生物的皮肤制作,我先将漫反射贴图用作SSS贴图,而后减少了饱和度并将其设置为BSDF着色器的SSS色彩。 而后增加其余的maps。尽管着色器应用并不简单,然而想要达到预期的成果还是有些麻烦,所以我始终在尝试很多不同的资料。 我在网络上看到了很多Blender中的皮肤着色教程,或者是制作十分酷的成果。另外,请确保皮肤纹理具备良好的质感-如果在着色之前看起来不错,则在制作材质时看起来会更好。 通过测试之后,最终我抉择在Cycles中进行渲染,并在Photoshop中测试了各种背景并应用了一些烟雾笔刷和进行了一些简略的后期制作和色彩校对。并进步了曝光度和对比度,并升高了饱和度,在尝试各种值吼,我失去了不错的后果。 最初,我心愿这些货色能帮到您,并启发您将本人的想法变为事实。感激观看!

February 26, 2021 · 1 min · jiezi

关于动画:使用ZBrushSubstance-Painter和Blender创建一只猩猩拳击手2

By:里卡多·维亚纳(Ricardo Viana) 3D角色艺术家Ricardo Viana借助Primate和Sportsperson应用ZBrush,Substance Painter和Blender制作了一只猩猩拳击手“愤恨香蕉”的第二部。这个模型最终还应用了Blender 的Cycles渲染器(译者注:Renderbus云渲染反对Blender渲染)进行渲染。 应用工具:ZBrush,Substance Painter和Blender,Cycles渲染器 最终设计当初,这只猩猩拳击手的形状曾经靠近实现,接下来就能够开始服装和配饰的工作。我寻找了一些游戏搏斗角色作为参考,请留神,我已依据敌人的倡议调整了设计,而后批改了圆形的四肢和身材的轮廓。请记住,参考资料是为了帮忙咱们解决设计问题,然而最终的后果应该遵循咱们的设计。 目前,模型的这些色彩只是临时的,但请留神他胸部的圆形以及在脸上绘制色彩形态的形式,如果确定轮廓形态,我会保留这些值。 参考资料中有《街头霸王》中的角色维克托·沙加特,《Charlie Bone》系列,《拳皇》系列Joe Higashi和泰拳王播求(buakaw)。 雕刻欠缺最终设计所有的设计都曾经实现,当初要确定的是最终渲染的成果。清理好所有的模型成果之后,我细化了一些货色:例如主要形态和解剖构造的细节。这样能够保障身材的解构整洁并且不会影响整体的形态。ZBrush屏幕截图显示了中层细节,例如右臂上的疤痕以及臂和腹部的褶皱。 这样,我在它的身材和道具上增加了一些更轻微的细节,比方身材上的一些疤痕和毛孔等,道具上的一些划痕,磨损等。 最终后果这是咱们的最终后果!正如我之前提到的,我将具体步骤放弃在细节上。应用Substance Painter,在纹理化步骤中解决这些较小的细节时,我感到更自在。因为咱们不须要将这些利用于位移修改器,因而它通常能够完满地与法线贴图和粗糙度/根底色彩贴图一起应用。对于身材根底,我应用头发颗粒在宏观档次上进行了具体阐明。两头细节的不足有助于放弃根本形态的坚硬,而宏观细节则为最终后果减少了可信度。 请留神,较大的形态看起来很松软,缩放后能够看到宏观细节,外表之间的对比度优先思考整体印象。 最重要的秘诀-放弃简略 这听起来很显著,然而对于简洁的设计而言,要害是要做到这一点。留神不要太简略并制作平坦的黑白外表,多样性十分重要。您应该重点关注同质性的印象。设置基色,而后次要在混合模式下应用。专一于色彩,而后再扩散其余地图的注意力。请留神整体看起来平均,然而放大后您会看到外表有些简单。

February 25, 2021 · 1 min · jiezi

关于动画:用Blender设计插画3D艺术家又开始秀啦

还记得入围第92届奥斯卡金像奖最佳动画长片的电影《我失去了身材》(用Blender三渲二打造一只“断手”的旅行,真香!),尽管看起来像2D动画,但其实是应用Blender进行3D制作的。尽管用Blender模仿2D视觉效果已不是什么新鲜事,但近日又有一位3D艺术家,用Blender制作了一个插画格调的场景,一起来看看他是怎么做到的吧! Kürşad Karataş在CG行业已有超过22年的从业教训,他是一名3D艺术家/3D模型雕刻师/动画师/通才,有过游戏、电影、医疗CG等CG我的项目的制作教训。目前,他在钻研用于机器学习和机器视觉目标的合成图像畛域工作,并在Zumolabs为Blender开发插件和辅助工具。 Karataş从2005年开始学习Blender,过后的Blender不能与其余行业标准3D软件等量齐观。那时他次要把Blender当作建模工具,直到2012年左右,他开始开发Blender插件。 他最喜爱的Blender的性能是(实际上,是Blender所有性能) :游标(The cursor) 、导入 bpy、gzz1/10, rxx30。 制作的灵感和指标Inspiration and goalsKarataş最后的指标是创作一个非写实的插画作品。因而,他又想到了另外两个指标。 艺术指标想出一个能够用EEVEE实现的时尚外观作品的故事应该是对于对抗的力量和格调格调应该像老式的科幻小说封面技术指标在几何体载荷方面冲破EEVEE的极限疾速创立简单的几何体提出不同的程序化创立资产办法尝试一些非写实格调的布光形式不要应用Freestyle(卡通渲染)用于线条解决或适度应用合成技巧流程Process这个作品有很丰盛的内容形成。作品中有很多元素,所以Karataş把握整体的内容,不会关注每一个细节。 草图Sketch 他最后先画一个非常简单的草图,没有收集任何参考资料,也没有依赖任何其余灵感,营造一种独特的气氛。一旦决定了构图中搁置的形态大小及地位,就开始创立初始的布局。 仿人机器人Humanoids 仿人机器人叛军兵士是画面的次要亮点,所以,Karataş将他们设计在离观看者最近的地位。它们其实是我的项目中比拟小的形成元素,所以将它们作为一组放在图像中的上面,就显得绝对重要了。 Karataş应用简略的几何形态创作他们大抵的外观。首先,依据所需的整体格调创作了兵士模型原型,而后应用雕刻工具和编辑模式从新给其余的角色模型摆pose,不应用任何绑定工具来放弃他们自在的状态,尽可能放弃涣散的几何形态,有点插画内味。 之后应用置换修改器(Displacement Modifier)增加一些轻微的变动,同样地,利用到兵士的武器上。此外,他应用镜像修改器(Mirror Modifier)来制作一些变动。最初,Karataş设计了上图中的三种武器。 初始布局Initial layout Karataş首先对次要构造进行总体布局,为了强调王国总部松软的进攻根底,他设计了新月形山脉围绕王国。地形和山脉都须是毛糙的几何形态,能力很好的配合他所需的材质格调。他应用低分辨率的立体制作毛糙的几何形态,应用位移修改器(Displacement Modifier)、平滑修改器(Smooth Modifier)和精简修改器(Decimate Modifier),增加更多人造的细节。 叛军战舰Rebel ships Karataş要为敌方战舰设计成尖利的形态,所以他决定为叛军战舰设计得圆润一些,以便与敌方战舰造成比照,这样就能够在空战时清晰的区别两方。 前述的叛军兵士设计办法同样利用到叛军战舰的设计上,但这个设计须要更多细节。须要更多细节的起因是它们离观看者绝对近。这就是为什么他依赖kitbashing技术雕刻建模。Karataş应用了之前我的项目中的一些模型来设计这艘飞船,以营造规模感和体积感。须要留神的一点是,因为格调的抉择,不须要电影级别的几何细节。在这里应用带Bisect选项的镜像修改器(Mirror Modifier)尝试应用不同的局部做不同的设计 。镜像修改器(Mirror Modifier)设置为应用自定义对象的核心和旋转,以便尝试不同的想法的时候能够轻松的挪动和旋转镜像核心。 敌方战舰Enemy ships 敌方战舰必须看起来很乏味,所以Karataş首先关注它们的轮廓。为了给它们打造一个有威胁性的外观,他在模型上设计了尖尖的翅膀和类昆虫的特色。对于这些设计,他再次应用带Bisect选项的镜像修改器(Mirror Modifier)。 Karataş还心愿敌方战舰在基地四周飞来飞去,向地面的叛军战舰发射激光。在Blender中实现这场大规模的太空战绝对容易。他在一个椭圆的球体上增加了一个体积粒子系统,应用collection(汇合)性能会集敌方战舰。 Blender的粒子系统能够让你抉择任何汇合作为具备随机抉择的实例源。 基地The Base 王国的宫殿修建次要的设计指标是带有威胁性和看起来略显怪异的外观。这就是为什么Karataş想在宫殿的模型中退出一个头骨的形态。他应用了几年前的一个树脂头骨的三维扫描模型。而后把网格导入到Blender中,开始设计它。头骨的原始形态看起来过于有机和有弧度。应用精简修改器(Decimate Modifier)能够去除其中一些形态。通过一些额定的清理工作,失去了一个看起来像头骨的建筑物。 一旦大抵的形态好了,就在它四周搁置一些根本形态。这样做的同时,Karataş感觉到这个构造短少了比例感,于是他开始在四周增加小的细节。细节自身并不重要,重要是它们能为整体削减一些价值。这是一个有肯定间隔物体,整体轮廓必须是可辨认的并有弱小的力量感。 其余构造及车辆Other structures and vehicles为了让场景和故事更有深度,须要有更多的构造和车辆。Karataş做了大量的工作来增加第三层的细节,如下所示。 他次要通过kitbashing技术和模型刮削来制作第三层级背景内容,同时要确保它们合乎我的项目的总体感觉。他应用粒子实例修改器(Particle Instance Modifier)和阵列修改器(Array Modifiers)将它们填充到场景中。 着色Shading对于惯例着色,Karataş抉择了原理着色器(Principled Shader)和自定义配色计划的简略组合,这些着色器是基于面的法线的。 ...

February 19, 2021 · 1 min · jiezi

关于动画:解锁Renderbus客户端使用新技巧快速渲染效果图篇

度娘说,效果图最根本的要求就是:应该合乎事物的自身尺寸,不能为了好看而应用成果把相干模型的尺寸变动,那样的效果图岂但不能起到体现设计的作用,反而成为影响设计的一个因素。可见高效渲染效果图是都是当下咱们最须要的。 有没有发现,客户端拖拽式提交尽管更加不便于多个场景文件批量提交,一键拖拽后,坐等后果即可,然而若想要一边制作,一边提交的话 是不是感觉客户端拖拽式提交,剖析工夫有丢丢久呢? 是不是感觉客户端拖拽式提交,须要找到源文件门路的要求很刻薄呢? 是不是想要get到更快更便捷的渲染效果图的办法呢? 小编带您解锁效果图提交渲染的最新姿态。 上面要给大家介绍操作的根本流程啦: 操作详情如下: 1.点击进入渲染入口; 2.依据理论状况,确认软件装置门路,装置插件,只需装置一次,就能够始终应用,无需反复装置~ 3.关上3ds Max软件,在工具栏找到瑞云渲染,抉择公布工作选项。 4.在设置窗口,进行效果图的具体设置。 重重重点阐明: 开启极速渲染性能,采纳多台机器进行分布式渲染,带你体飞个别的渲染速度~ 保留门路 在此处设置文件输入门路和输入的文件名,渲染实现后,渲染后果即会主动下载到此目录中。 5.设置实现后,点击提交按钮,实现后,即可在客户端“我的上传”模块,查看工作的上传进度。省去了剖析工作的工夫,大大提高了渲染效率。 6.上传胜利后,即可开始进行渲染工作。渲染实现后,即可间接在本地查看渲染后果。

January 12, 2021 · 1 min · jiezi

关于动画:Android动画系列之属性动画

原文首发于微信公众号:jzman-blog,欢送关注交换!属性动画相较帧动画和补间动画更弱小,帧动画和补间动画只能利用于 View 及其子类,而属性动画能够批改任何对象的属性值,属性值可在指定的一段时间内主动扭转,依据对象属性值的变动进而实现更简单的动画。 属性动画的罕用设置ValueAnimatorObjectAnimator关键帧插值器和估值器属性动画的罕用设置上面是属性动画的罕用设置,具体如下: //设置属性动画持续时间animator.setDuration(2000);//设置属性插值器animator.setInterpolator(new AccelerateInterpolator());//设置属性动画反复播放模式animator.setRepeatMode(ValueAnimator.REVERSE);//设置属性动画反复播放次数animator.setRepeatCount(0);//设置属性动画延时播放的工夫animator.setStartDelay(0);//设置属性动画估值器,用于管制最终属性值(API22)animator.setCurrentFraction(0.5f);//设置以后播放工夫,其值在Duration范畴之内animator.setCurrentPlayTime(1000);//设置属性动画估值器,用于管制最终属性值animator.setEvaluator(new IntEvaluator());//设置属性动画监听animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Log.i(TAG, animation.getAnimatedValue() + ""); // }});//...ValueAnimatorValueAnimator 提供了一个简略的计时引擎,用于执行动画时依据设置的时长以及其余属相实现动画值的计算,而后就能够将动画值设置到适合的指标对象上,应用的插值器默认时 AccelerateDecelerateInterpolator,示意动画开始和完结时较慢,两头减速实现动画,上面是源码中默认的插值器,具体如下: // The time interpolator to be used if none is set on the animationprivate static final TimeInterpolator sDefaultInterpolator = new AccelerateDecelerateInterpolator();在 ValueAnimator 中曾经外部解决了一些估值器 IntEvaluator 和 FloatEvaluator,也就是说如果应用的时 ofInt 和 ofFloat 办法作为动画的属性值,那么 ValueAnimator 会主动解决 int 和 float 值的变动,在源码中找了一下,这部分内容在 PropertyValuesHolder 这个类中,具体如下: void init() { if (mEvaluator == null) { // We already handle int and float automatically, but not their Object // equivalents mEvaluator = (mValueType == Integer.class) ? sIntEvaluator : (mValueType == Float.class) ? sFloatEvaluator : null; } if (mEvaluator != null) { // KeyframeSet knows how to evaluate the common types - only give it a custom // evaluator if one has been set on this class mKeyframes.setEvaluator(mEvaluator); }}ValueAnimator 能够应用代码创立,也能够应用 xml 创立,上面以平移动画为例阐明 ValueAnimator 的应用形式,其余如缩放、旋转等应用形式相似。 ...

December 1, 2020 · 3 min · jiezi

关于动画:优惠提醒小助手

UWA学堂目前曾经上线90门课程,涵盖了渲染、UI、动画、资源管理等多个模块的内容,明天就带大家来看看,还在优惠期的课程有哪些吧! 《大规模动画模仿的一种实现形式》 8月17日复原原价49元,现价39元 你也想实现大规模动画模仿吗?看着他人场景的动态效果心动吗? 想在凋谢场景中实现大规模的动画晋升场景的生机,减少场景细节以及丰盛水平?本课程带你从思考问题开始,剖析各种实现形式的利弊,并参考了《神秘海域4》的技术分享,最终实现了一套计划。残缺地还原了思考问题以及解决问题的过程。 课程前5节均可收费试读第6节提供了Demo所有的外围代码。抛砖引玉,本文展现的是蝴蝶群的场景动画,课程实现的成果并不只局限于此,能够触类旁通将其利用到其它相似的需要上,减少场景的丰盛水平和生机。 《Unity HDRP烘焙技术原理及利用》 8月24日复原原价29元,现价12元 照片级渲染的基石,3A画质的第一步! 作者作为国内首批应用HDRP进行产品制作的开发者,凭借本人多年的开发教训以及对HDRP的深刻理解,将本课程分为9个大节,向咱们全面、具体地展现了HDRP烘焙的技术原理及利用,并在文末附上了课程DEMO,边学习边实现,直观感触HDRP带来的真实感! 《机器学习驱动的游戏AI 利用流程指南》 8月31日复原原价49元,现价29元 来玩一局卡丁车????,本人发明一个AI 对手一起较量吧? 本期的课程中,咱们将一起实现一款应用机器学习AI对手的卡丁车游戏(基于Unity官网收费美术资产)。游戏具备理论可玩性,玩家的对手齐全是通过机器学习过程创立的。 WebGL游戏试玩(挪动端暂不反对体验)>>http://auk5qf.coding-pages.com

August 5, 2020 · 1 min · jiezi

关于动画:优惠提醒小助手

UWA学堂目前曾经上线90门课程,涵盖了渲染、UI、动画、资源管理等多个模块的内容,明天就带大家来看看,还在优惠期的课程有哪些吧! 《大规模动画模仿的一种实现形式》 8月17日复原原价49元,现价39元 你也想实现大规模动画模仿吗?看着他人场景的动态效果心动吗? 想在凋谢场景中实现大规模的动画晋升场景的生机,减少场景细节以及丰盛水平?本课程带你从思考问题开始,剖析各种实现形式的利弊,并参考了《神秘海域4》的技术分享,最终实现了一套计划。残缺地还原了思考问题以及解决问题的过程。 课程前5节均可收费试读第6节提供了Demo所有的外围代码。抛砖引玉,本文展现的是蝴蝶群的场景动画,课程实现的成果并不只局限于此,能够触类旁通将其利用到其它相似的需要上,减少场景的丰盛水平和生机。 《Unity HDRP烘焙技术原理及利用》 8月24日复原原价29元,现价12元 照片级渲染的基石,3A画质的第一步! 作者作为国内首批应用HDRP进行产品制作的开发者,凭借本人多年的开发教训以及对HDRP的深刻理解,将本课程分为9个大节,向咱们全面、具体地展现了HDRP烘焙的技术原理及利用,并在文末附上了课程DEMO,边学习边实现,直观感触HDRP带来的真实感! 《机器学习驱动的游戏AI 利用流程指南》 8月31日复原原价49元,现价29元 来玩一局卡丁车????,本人发明一个AI 对手一起较量吧? 本期的课程中,咱们将一起实现一款应用机器学习AI对手的卡丁车游戏(基于Unity官网收费美术资产)。游戏具备理论可玩性,玩家的对手齐全是通过机器学习过程创立的。 WebGL游戏试玩(挪动端暂不反对体验)>>http://auk5qf.coding-pages.com

August 5, 2020 · 1 min · jiezi

关于动画:大规模动画模拟的一种实现方法

“动态的场景看着没生机,他人家的场景都是动静的,还能和角色互动,咱们也加一个吧?花丛里加上昆虫、蝴蝶;夜晚有萤火虫飞舞,角色走过就会散开......”“停停停,需要能够有,但实现起来可没有咱们设想地那么简略,就以在花丛中刷上昆虫蝴蝶为例来看吧,咱们须要.....” https://edu.uwa4d.com/course-intro/0/182 文章简介: 依据在凋谢场景我的项目中实现大规模的蝴蝶动画来晋升场景的生机,减少场景细节和丰盛水平的需要。本课程从思考问题开始,剖析各种实现形式的利弊,参考《神秘海域4》的技术分享,最终实现了一套计划。残缺地还原了思考问题以及解决问题的过程。 第6节提供了Demo所有的外围代码,并通过Unity 2019.4LTS、iPhone 6s测试通过。作为抛砖引玉,课程实现的成果并不只局限于此,能够触类旁通将其利用到其它相似的需要上,减少场景的丰盛水平和生机。 阐明:本课程共6大节,字数约8600字,浏览时长约为40分钟。本课程第1~5节均可收费试读,残缺理解课程内容再“下手”。 作者简介:程可汗:毕业于江苏广播电视大学,机械电子业余。工作经验:奇拓动漫,蕴衡网络,游戏蜗牛,边锋游戏,雪彩网络。  曾参加过多款游戏我的项目的研发工作:Flash小游戏《Flash Social Game》、战旗类页游《抉择》、RPG页游《梦幻物语》、ARPG页游《诸仙列传OL》、《关云长》和《侠客风波传》等手游我的项目。

July 17, 2020 · 1 min · jiezi

vue-transition-中Velocity的参数的观点

Vue transition 过渡动画使用钩子函数钩子函数将钩子函数绑定在transition标签上之后 <transition v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:after-enter="afterEnter" v-on:enter-cancelled="enterCancelled" v-on:before-leave="beforeLeave" v-on:leave="leave" v-on:after-leave="afterLeave" v-on:leave-cancelled="leaveCancelled" > <!-- ... --> </transition>在Vue实例中的methods定义钩子函数的方法 在这里踩到了一个坑after-enter函数一直没有触发,花了很久时间才得到解决,这里用了Vue官方推荐的插件Velocity.js,现在就让我们聊聊这个问题 原来有问题的代码 enter(el, done){ Velocity(el, { translateX: '150px',translateY : '450px' }, { duration: 1000 }) Velocity(el,{opacity: 1},{ duration: 1000 }, { complete: done }) // setTimeout(() => { // el.offsetWidth // el.style.transform = "translate(150px,450px)"; // el.style.transition = "all 2s easy"; // done() // }, 20) },上面代码的动画效果没有问题,但是after-enter方法却一直没有触发后面通过调试后发现 Velocity(el,{opacity: 1},{ duration: 1000 }, { complete: done })改为 ...

July 13, 2019 · 2 min · jiezi

AE动画转Web代码工具指北Lottie

简介Lottie 是 Airbnb 开源的一套跨平台的完整的动画效果解决方案,设计师可以使用 Adobe After Effects 设计出漂亮的动画之后,使用 Lottic 提供的 Bodymovin 插件将设计好的动画导出成 JSON 格式,就可以直接运用在 iOS、Android、Web 和 React Native之上,无需其他额外操作。简单来说,Lottie就是一个可以将AE动画转成可运行在IOS、Android、Web、React Native上的AE插件。 使用工具使用前请确保已安装这以下工具。 插件:bodymovin.zxp解压:ZXP Installer其他。 Lottie预览:https://www.lottiefiles.com/p...web bodymovin cdn: https://cdnjs.com/libraries/b...使用步骤安装并解压bodymovin打开AE,添加bodymovin扩展导出data.json文件详细可参考:炫酷神器,AE插件Bodymovin.zxp的安装与使用 API由bodymovin导出的data.json实际就是动画的数据文件,我们引入的bodymovin.js库会对其做相应的解析。接下来我们只需要添加简单的初始化代码就可以在运行代码看到相应的动画效果了。 以下是最常用的api 1. 初始化let animation = bodymovin.loadAnimation({ animationData, // [必须] data.json文件 path, // data.json文件路径(animationData/path选其一传入即可) container,// [必须] 父容器 renderer, // [必须] 渲染方式 loop, autoplay})2. 暂停/停止/播放bodymovin.play()bodymovin.pause()bodymovin.stop() // 回到第0帧3. 跳转之某帧并播放animation.gotoAndStop(time)OR animation.gotoAndStop(frame)----animation.gotoAndPlay(time)OR animation.gotoAndPlay(frame)4. 设置fpanimation.setSubframe()// true: 使用本地环境的fps [默认]// false: 使用ae原本的fps5. 事件监听animation.onComplete = function () {} // 动画结束animation.onLoopComplete = function () {} // 当前循环结束// 使用addEventListener方式animation.addEventListener('complete', function () {})animation.addEventListener('loopComplete', function () {})一般来说以上的api即可满足大部分的动画展示需求了。更详细内容可参考官网~ ...

April 30, 2019 · 1 min · jiezi

一次react-router + react-transition-group实现转场动画的探索

原文地址1. Introduction在日常开发中,页面切换时的转场动画是比较基础的一个场景。在react项目当中,我们一般都会选用react-router来管理路由,但是react-router却并没有提供相应的转场动画功能,而是非常生硬的直接替换掉组件。一定程度上来说,体验并不是那么友好。为了在react中实现动画效果,其实我们有很多的选择,比如:react-transition-group,react-motion,Animated等等。但是,由于react-transition-group给元素添加的enter,enter-active,exit,exit-active这一系列勾子,简直就是为我们的页面入场离场而设计的。基于此,本文选择react-transition-group来实现动画效果。接下来,本文就将结合两者提供一个实现路由转场动画的思路,权当抛砖引玉~2. Requirements我们先明确要完成的转场动画是什么效果。如下图所示:3. react-router首先,我们先简要介绍下react-router的基本用法(详细看官网介绍)。这里我们会用到react-router提供的BrowserRouter,Switch,Route三个组件。BrowserRouter:以html5提供的history api形式实现的路由(还有一种hash形式实现的路由)。Switch:多个Route组件同时匹配时,默认都会显示,但是被Switch包裹起来的Route组件只会显示第一个被匹配上的路由。Route:路由组件,path指定匹配的路由,component指定路由匹配时展示的组件。// src/App1/index.jsexport default class App1 extends React.PureComponent { render() { return ( <BrowserRouter> <Switch> <Route exact path={’/’} component={HomePage}/> <Route exact path={’/about’} component={AboutPage}/> <Route exact path={’/list’} component={ListPage}/> <Route exact path={’/detail’} component={DetailPage}/> </Switch> </BrowserRouter> ); }}如上所示,这是路由关键的实现部分。我们一共创建了首页,关于页,列表页,详情页这四个页面。跳转关系为:首页 ↔ 关于页首页 ↔ 列表页 ↔ 详情页来看下目前默认的路由切换效果:4. react-transition-group从上面的效果图中,我们可以看到react-router在路由切换时完全没有过渡效果,而是直接替换的,显得非常生硬。正所谓工欲善其事,必先利其器,在介绍实现转场动画之前,我们得先学习如何使用react-transition-group。基于此,接下来就将对其提供的CSSTransition和TransitionGroup这两个组件展开简要介绍。4.1 CSSTransitionCSSTransition是react-transition-group提供的一个组件,这里简单介绍下其工作原理。When the in prop is set to true, the child component will first receive the class example-enter, then the example-enter-active will be added in the next tick. CSSTransition forces a reflow between before adding the example-enter-active. This is an important trick because it allows us to transition between example-enter and example-enter-active even though they were added immediately one after another. Most notably, this is what makes it possible for us to animate appearance.这是来自官网上的一段描述,意思是当CSSTransition的in属性置为true时,CSSTransition首先会给其子组件加上xxx-enter的class,然后在下个tick时马上加上xxx-enter-active的class。所以我们可以利用这一点,通过css的transition属性,让元素在两个状态之间平滑过渡,从而得到相应的动画效果。相反地,当in属性置为false时,CSSTransition会给子组件加上xxx-exit和xxx-exit-active的class。(更多详细介绍可以戳官网查看)基于以上两点,我们是不是只要事先写好class对应的css样式即可?可以做个小demo试试,如下代码所示:// src/App2/index.jsexport default class App2 extends React.PureComponent { state = {show: true}; onToggle = () => this.setState({show: !this.state.show}); render() { const {show} = this.state; return ( <div className={‘container’}> <div className={‘square-wrapper’}> <CSSTransition in={show} timeout={500} classNames={‘fade’} unmountOnExit={true} > <div className={‘square’} /> </CSSTransition> </div> <Button onClick={this.onToggle}>toggle</Button> </div> ); }}/* src/App2/index.css /.fade-enter { opacity: 0; transform: translateX(100%);}.fade-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.fade-exit { opacity: 1; transform: translateX(0);}.fade-exit-active { opacity: 0; transform: translateX(-100%); transition: all 500ms;}来看看效果,是不是和页面的入场离场效果有点相似?<div align=“center”> <img src=“https://upload-images.jianshu…;/></div>4.2 TransitionGroup用CSSTransition来处理动画固然很方便,但是直接用来管理多个页面的动画还是略显单薄。为此我们再来介绍react-transition-group提供的TransitionGroup这个组件。The <TransitionGroup> component manages a set of transition components (<Transition> and <CSSTransition>) in a list. Like with the transition components, <TransitionGroup> is a state machine for managing the mounting and unmounting of components over time.如官网介绍,TransitionGroup组件就是用来管理一堆节点mounting和unmounting过程的组件,非常适合处理我们这里多个页面的情况。这么介绍似乎有点难懂,那就让我们来看段代码,解释下TransitionGroup的工作原理。// src/App3/index.jsexport default class App3 extends React.PureComponent { state = {num: 0}; onToggle = () => this.setState({num: (this.state.num + 1) % 2}); render() { const {num} = this.state; return ( <div className={‘container’}> <TransitionGroup className={‘square-wrapper’}> <CSSTransition key={num} timeout={500} classNames={‘fade’} > <div className={‘square’}>{num}</div> </CSSTransition> </TransitionGroup> <Button onClick={this.onToggle}>toggle</Button> </div> ); }}我们先来看效果,然后再做解释:对比App3和App2的代码,我们可以发现这次CSSTransition没有in属性了,而是用到了key属性。但是为什么仍然可以正常工作呢?在回答这个问题之前,我们先来思考一个问题:由于react的dom diff机制用到了key属性,如果前后两次key不同,react会卸载旧节点,挂载新节点。那么在上面的代码中,由于key变了,旧节点难道不是应该立马消失,但是为什么我们还能看到它淡出的动画过程呢?关键就出在TransitionGroup身上,因为它在感知到其children变化时,会先保存住即将要被移除的节点,而在其动画结束时才会真正移除该节点。所以在上面的例子中,当我们按下toggle按钮时,变化的过程可以这样理解:<TransitionGroup> <div>0</div></TransitionGroup>⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️<TransitionGroup> <div>0</div> <div>1</div></TransitionGroup>⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️<TransitionGroup> <div>1</div></TransitionGroup>如上所解释,我们完全可以巧妙地借用key值的变化来让TransitionGroup来接管我们在过渡时的页面创建和销毁工作,而仅仅需要关注如何选择合适的key值和需要什么样css样式来实现动画效果就可以了。5. Page transition animation基于前文对react-router和react-transition-group的介绍,我们已经掌握了基础,接下来就可以将两者结合起来做页面切换的转场动画了。在上一小节的末尾有提到,用了TransitionGroup之后我们的问题变成如何选择合适的key值。那么在路由系统中,什么作为key值比较合适呢?既然我们是在页面切换的时候触发转场动画,自然是跟路由相关的值作为key值合适了。而react-router中的location对象就有一个key属性,它会随着浏览器中的地址发生变化而变化。然而,在实际场景中似乎并不适合,因为query参数或者hash变化也会导致location.key发生变化,但往往这些场景下并不需要触发转场动画。因此,个人觉得key值的选取还是得根据不同的项目而视。大部分情况下,还是推荐用location.pathname作为key值比较合适,因为它恰是我们不同页面的路由。说了这么多,还是看看具体的代码是如何将react-transition-group应用到react-router上的吧:// src/App4/index.jsconst Routes = withRouter(({location}) => ( <TransitionGroup className={‘router-wrapper’}> <CSSTransition timeout={5000} classNames={‘fade’} key={location.pathname} > <Switch location={location}> <Route exact path={’/’} component={HomePage} /> <Route exact path={’/about’} component={AboutPage} /> <Route exact path={’/list’} component={ListPage} /> <Route exact path={’/detail’} component={DetailPage} /> </Switch> </CSSTransition> </TransitionGroup>));export default class App4 extends React.PureComponent { render() { return ( <BrowserRouter> <Routes/> </BrowserRouter> ); }}这是效果:App4的代码思路跟App3大致相同,只是将原来的div换成了Switch组件,而且还用到了withRouter。withRouter是react-router提供的一个高阶组件,可以为你的组件提供location,history等对象。因为我们这里要用location.pathname作为CSSTransition的key值,所以用到了它。另外,这里有一个坑,就是Switch的location属性。A location object to be used for matching children elements instead of the current history location (usually the current browser URL).这是官网中的描述,意思就是Switch组件会用这个对象来匹配其children中的路由,而且默认用的就是当前浏览器的url。如果在上面的例子中我们不给它指定,那么在转场动画中会发生很奇怪的现象,就是同时有两个相同的节点在移动。。。就像下面这样:这是因为TransitionGroup组件虽然会保留即将被remove的Switch节点,但是当location变化时,旧的Switch节点会用变化后的location去匹配其children中的路由。由于location都是最新的,所以两个Switch匹配出来的页面是相同的。好在我们可以改变Switch的location属性,如上述代码所示,这样它就不会总是用当前的location匹配了。6. Page dynamic transition animation虽然前文用react-transition-group和react-router实现了一个简单的转场动画,但是却存在一个严重的问题。仔细观察上一小节的示意图,不难发现我们的进入下个页面的动画效果是符合预期的,但是后退的动画效果是什么鬼。。。明明应该是上个页面从左侧淡入,当前页面从右侧淡出。但是为什么却变成当前页面从左侧淡出,下个页面从右侧淡入,跟进入下个页面的效果是一样的。其实错误的原因很简单:首先,我们把路由改变分成forward和back两种操作。在forward操作时,当前页面的exit效果是向左淡出;在back操作时,当前页面的exit效果是向右淡出。所以我们只用fade-exit和fade-exit-active这两个class,很显然,得到的动画效果肯定是一致的。因此,解决方案也很简单,我们用两套class来分别管理forward和back操作时的动画效果就可以了。/ src/App5/index.css // 路由前进时的入场/离场动画 /.forward-enter { opacity: 0; transform: translateX(100%);}.forward-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.forward-exit { opacity: 1; transform: translateX(0);}.forward-exit-active { opacity: 0; transform: translateX(-100%); transition: all 500ms;}/ 路由后退时的入场/离场动画 */.back-enter { opacity: 0; transform: translateX(-100%);}.back-enter-active { opacity: 1; transform: translateX(0); transition: all 500ms;}.back-exit { opacity: 1; transform: translateX(0);}.back-exit-active { opacity: 0; transform: translate(100%); transition: all 500ms;}不过光有css的支持还不行,我们还得在不同的路由操作时加上合适的class才行。那么问题又来了,在TransitionGroup的管理下,一旦某个组件挂载后,其exit动画其实就已经确定了,可以看官网上的这个issue。也就是说,就算我们动态地给CSSTransition添加不同的ClassNames属性来指定动画效果,但其实是无效的。解决方案其实在那个issue的下面就给出了,我们可以借助TransitionGroup的ChildFactory属性以及React.cloneElement方法来强行覆盖其className。比如:<TransitionGroup childFactory={child => React.cloneElement(child, { classNames: ‘your-animation-class-name’})}> <CSSTransition> … </CSSTransition></TransitionGroup>上述几个问题都解决之后,剩下的问题就是如何选择合适的动画class了。而这个问题的实质在于如何判断当前路由的改变是forward还是back操作了。好在react-router已经贴心地给我们准备好了,其提供的history对象有一个action属性,代表当前路由改变的类型,其值是’PUSH’ | ‘POP’ | ‘REPLACE’。所以,我们再调整下代码:// src/App5/index.jsconst ANIMATION_MAP = { PUSH: ‘forward’, POP: ‘back’}const Routes = withRouter(({location, history}) => ( <TransitionGroup className={‘router-wrapper’} childFactory={child => React.cloneElement( child, {classNames: ANIMATION_MAP[history.action]} )} > <CSSTransition timeout={500} key={location.pathname} > <Switch location={location}> <Route exact path={’/’} component={HomePage} /> <Route exact path={’/about’} component={AboutPage} /> <Route exact path={’/list’} component={ListPage} /> <Route exact path={’/detail’} component={DetailPage} /> </Switch> </CSSTransition> </TransitionGroup>));再来看下修改之后的动画效果:7. Optimize其实,本节的内容算不上优化,转场动画的思路到这里基本上已经结束了,你可以脑洞大开,通过添加css来实现更炫酷的转场动画。不过,这里还是想再讲下如何将我们的路由写得更配置化(个人喜好,不喜勿喷)。我们知道,react-router在升级v4的时候,做了一次大改版。更加推崇动态路由,而非静态路由。不过具体问题具体分析,在一些项目中个人还是喜欢将路由集中化管理,就上面的例子而言希望能有一个RouteConfig,就像下面这样:// src/App6/RouteConfig.jsexport const RouterConfig = [ { path: ‘/’, component: HomePage }, { path: ‘/about’, component: AboutPage, sceneConfig: { enter: ‘from-bottom’, exit: ’to-bottom’ } }, { path: ‘/list’, component: ListPage, sceneConfig: { enter: ‘from-right’, exit: ’to-right’ } }, { path: ‘/detail’, component: DetailPage, sceneConfig: { enter: ‘from-right’, exit: ’to-right’ } }];透过上面的RouterConfig,我们可以清晰的知道每个页面所对应的组件是哪个,而且还可以知道其转场动画效果是什么,比如关于页面是从底部进入页面的,列表页和详情页都是从右侧进入页面的。总而言之,我们通过这个静态路由配置表可以直接获取到很多有用的信息,而不需要深入到代码中去获取信息。那么,对于上面的这个需求,我们对应的路由代码需要如何调整呢?请看下面:// src/App6/index.jsconst DEFAULT_SCENE_CONFIG = { enter: ‘from-right’, exit: ’to-exit’};const getSceneConfig = location => { const matchedRoute = RouterConfig.find(config => new RegExp(^${config.path}$).test(location.pathname)); return (matchedRoute && matchedRoute.sceneConfig) || DEFAULT_SCENE_CONFIG;};let oldLocation = null;const Routes = withRouter(({location, history}) => { // 转场动画应该都是采用当前页面的sceneConfig,所以: // push操作时,用新location匹配的路由sceneConfig // pop操作时,用旧location匹配的路由sceneConfig let classNames = ‘’; if(history.action === ‘PUSH’) { classNames = ‘forward-’ + getSceneConfig(location).enter; } else if(history.action === ‘POP’ && oldLocation) { classNames = ‘back-’ + getSceneConfig(oldLocation).exit; } // 更新旧location oldLocation = location; return ( <TransitionGroup className={‘router-wrapper’} childFactory={child => React.cloneElement(child, {classNames})} > <CSSTransition timeout={500} key={location.pathname}> <Switch location={location}> {RouterConfig.map((config, index) => ( <Route exact key={index} {…config}/> ))} </Switch> </CSSTransition> </TransitionGroup> );});由于css代码有点多,这里就不贴了,不过无非就是相应的转场动画配置,完整的代码可以看github上的仓库。我们来看下目前的效果:8. Summarize本文先简单介绍了react-router和react-transition-group的基本使用方法;其中还分析了利用CSSTransition和TransitionGroup制作动画的工作原理;接着又将react-router和react-transition-group两者结合在一起完成一次转场动画的尝试;并利用TransitionGroup的childFactory属性解决了动态转场动画的问题;最后将路由配置化,实现路由的统一管理以及动画的配置化,完成一次react-router + react-transition-group实现转场动画的探索。9. ReferenceA shallow dive into router v4 animated transitionsDynamic transitions with react router and react transition groupIssue#182 of react-transition-groupStackOverflow: react-transition-group and react clone element do not send updated props本文所有代码托管在这儿,如果觉得不错的,可以给个star。 ...

April 14, 2019 · 4 min · jiezi

CSS 搞事技巧:hover+active

介绍在 JavaScript 中,我们可以利用变量,DOM 来保存状态,而 CSS 当中,我们也可以利用伪类选择器来保存状态,这就是 CSS 搞事的核心了。核心概念:保存状态。在上一篇 CSS 搞事技巧:checkbox+label+selector 中利用 checkbox+label+selector 来加深了解了 Flex 的弹性容器属性,这一节是要利用 :hover+:active 来了解 Flex 的弹性项目属性。这两个属性你有没有很熟悉呢,其实我们经常在 a 标签上使用它们LVHA 顺序: :link — :visited — :hover — :active效果图:示例源码、在线示例示例由于作者找不到工作,陷入自闭缺乏创作激情,所以这一个环节就略过……技巧说明hover 触发时,隐藏的子元素显示;active 触发时,子元素按照需求变化。代码解读1. 基础布局因为展示性的东西需要垂直居中展示,所以我利用 Vue 的 props 固化了垂直居中的样式:<Layout-Layout align-center justify-center :background-color=“bgColor”> hello flex item</Layout-Layout>为了更容易演示,有请高矮胖瘦均不一致的三兄弟:<div class=“flex__item”>大哥</div><div class=“flex__item”>二弟</div><div class=“flex__item”>三妹</div>为它们增加样式,并添加伪元素:<style lang=“stylus” scoped>.flex__item width 15% height 15% box-shadow 0 0 4px rgba(0, 0, 0, .5), inset 2px 0 1px rgba(0, 0, 0, .2) display flex align-items center justify-content center color #142334 // 钢青 &:nth-child(1) width 20% height 20% &:nth-child(2) width 16% height 18% &:nth-child(3) width 14% height 28% &::before position absolute content ‘一人得道’ padding 10px 6px border-radius 6px color #e0c8d1 // 淡青紫 background-color #1661ab // 靛青 &::after position absolute content ‘背水一战’ padding 10px 6px border-radius 6px color #e0c8d1 // 淡青紫 background-color #1661ab // 靛青</style>查看一下效果:2. :hover基础布局完成,接着是添加 :hover 效果。当鼠标悬停时,两个伪元素将会显示,并且一个往上一个往下:.flex__item &::before opacity 0 &::after opacity 0.flex__item:hover::before transform translateY(-80px) opacity 1.flex__item:hover::after transform translateY(80px) opacity 1查看效果:3. :active在我的记忆中,:active 是对任何元素都生效的,结果伪元素上设置失败了,然后就去看了下 MDN:或许伪元素与元素本身算作一体?还是说要选择到父元素(线索::focus-within)?这个留之后解决吧,FLag +1。取舍的办法还是有的(伪装),牺牲带头大哥吧:.flex__item &:nth-child(1) width 20% height 20% &::after position absolute content ‘背水一战’ padding 10px 6px border-radius 6px color #e0c8d1 // 淡青紫 background-color #1661ab // 靛青 transition all 0.5s linear opacity 0 &:nth-child(2) width 16% height 18% &::before position absolute content ‘一人得道’ padding 10px 6px border-radius 6px color #e0c8d1 // 淡青紫 background-color #1661ab // 靛青 transition all 0.5s linear opacity 0 &:nth-child(3) width 14% height 28% &::before position absolute content ‘一人得道’ padding 10px 6px border-radius 6px color #e0c8d1 // 淡青紫 background-color #1661ab // 靛青 transition all 0.5s linear opacity 0查看效果:为伪类添加 :active 效果:.flex__item:active::before,.flex__item:active::after color #f1908c // 榴子红 background-color #fff box-shadow 0 2px 4px rgba(0, 0, 0, .5), 2px 0 4px rgba(0, 0, 0, .6)查看效果:4. align-self给子元素添加 align-self 不同的值:.flex__item &:nth-child(1) &:active align-self flex-end &:nth-child(2) &:active align-self flex-start &:nth-child(3) &:active align-self flex-start最后结果就如介绍中所示了。最后CSS 很多属性我们可能难以理解其效果,个人认为运用 CSS 来解释 CSS 不失为一种良好的方式。参考资料MDN中国色 ...

April 2, 2019 · 2 min · jiezi

CSS 搞事技巧:checkbox+label+selector

介绍其实这篇文章写到一大半时它的名字还叫做 《重温 Flex 布局》,结果写着写着就走了心,附上一图表示心情吧:其实我并不是一个喜欢搞事的人,真的,从我游戏:屠夫沉默(DOTA2),盖伦(LOL),亚瑟(农药)的英雄池完全可以看出来。这三个元素作为这个 《CSS 搞事技巧》的第一篇并非仅仅是在接下来的 《重温 Flex 布局》中要使用,而是因为这个技巧的确是很基础。核心概念:所谓技巧,无非就是保存状态(划重点啦)。示例示例部分仅放 Gif 图样及源码地址,详情请查看技巧说明部分(技巧是通用的)。1. switch 开关样式模仿的是 Vuetify 的 Switches 。Gif 预览:动画源码、在线动画2. 关灯效果上面实现了开关,那么一个简单的联想当然是开关灯啦。Gif 预览:动画源码、在线动画技巧说明在 JavaScript 中,我们可以利用变量,DOM 来保存状态,而 CSS 当中,我们也可以利用伪类选择器来保存状态,这就是 CSS 搞事的核心了,也是上面示例实现的本质。回到主题,也就是 Flex 布局来,我们初步回顾一下,Flex 布局的相关概念,首先被声明了 Flex 弹性布局的元素叫做弹性容器,其子元素被称为弹性项目。这个弹性容器中默认存在两个轴,一个叫做主轴(main axis),侧轴(cross axis)。整个 Flex 容器具有六个属性,此次仅介绍两个:justify-content 和 align-items (提一下 align-content 是多行的概念)。列出这个两个属性的常用值(看 MDN 可知已经增加了更多的值):.flex { justify-content: flex-start | flex-end | center | space-between | space-around; align-items: flex-start | flex-end | center;}代码解读该项目是通过 VuePress 来渲染的,所以会使用到 Vue 的语法,不过此处仅使用 Vue 的 for 循环来解决重复书写 DOM 的问题;该效果参考 来源 。1. label功能栏与展示栏分列两侧,首先是完成 DOM 结构:flex__feats 为左侧功能栏,flex__exh 为展示栏。<div class=“flex”> <section class=“flex__feats”> <div class=“feat__list”> <h4 class=“feat__list_title”>justify</h4> <div class=“feat__list_labels”> <label>flex-start</label> <label>flex-end</label> <label>center</label> <label>space-between</label> <label>space-around</label> </div> </div> <div class=“feat__list”> <h4 class=“feat__list_title”>align</h4> <div class=“feat__list_labels”> <label>flex-start</label> <label>flex-end</label> <label>center</label> </div> </div> </section> <div class=“divider”></div> <section class=“flex__exh”></section></div>接着使用 Flex 布局来将它们分割,因为这次主要将的不是 Flex,所以就不进行阐述了。<style lang=“stylus” scoped>.flex width 100% height 100% display flex * box-sizing border-box &__feats flex-basis 28% height 100% display flex justify-content space-around .feat__list display flex flex-direction column justify-content flex-start align-items center .feat__list_labels flex-grow 1 display flex flex-direction column justify-content space-around align-items center .divider width 1px height 100% margin 0 2px background-color #000;</style>可以注意到功能栏的 DOM 结构是重复的,使用 Vue 来简化一下吧:<section class=“flex__feats”> <div class=“feat__list” v-for="(feat, index) in feats()" :key=“index”> <h4 class=“feat__list_title”>{{feat.title}}</h4> <div class=“feat__list_labels”> <label v-for="(item, index) in feat.list" :key=“index”>{{item}}</label> </div> </div></section><script>export default { data() { return { bgColor: “#c0c4c3”, // 月影白 feats: () => { return [ {title: “jusitify”, list: [“flex-start”, “flex-end”, “center”, “space-between”, “space-around”]}, {title: “align”, list: [“flex-start”, “flex-end”, “center”]} ] } }; }};</script>2. checkbox接着需要一堆 checkbox 来触发状态,因为 DOM 解析是深度优先,CSS 中也没有父选择器这一说,虽然有一定的办法 hack 一下,所以这一堆 checkbox 就需要放在顶部:<div class=“flex”> <input type=“checkbox” class=“toggle” v-for="(checkbox, index) in checkboxs()" :id=“checkbox”></div>接着需要将其隐藏:.toggle[type=“checkbox”] width 0 height 0 filter opacity(0) opacity 0 display none3. selector要想生效的话,还需要将 input 的 id 绑定到 label 上:<label v-for="(item, index) in feat.list" :key=“index” :for="${feat.title}-${item}">{{item}}</label>接着利用 selector(选择器),获取到 label 并改变其颜色证明它是被选召的孩子,举个例子(完整版):#jusitify-flex-start:checked ~ .flex__feats .jusitify-flex-start padding 4px 4px background-color #5a191b // 栗紫 color #fffef9 // 雪白测试绑定情况:4. 右侧展示首先在其中添加三个元素,帮助我们观察效果:<section class=“flex__exh”> <div class=“exh__item” v-for=“n in 3” :key=“n”></div></section>&__exh flex-grow 1 padding 16px .exh__item width 15% height 15% box-shadow 0 0 4px rgba(0, 0, 0, .5) &:nth-child(1) width 18% height 20% &:nth-child(2) width 14% height 18% &:nth-child(3) width 15% height 15%接着继续使用 selector 来完成最后的任务,示例(完整版):#jusitify-flex-start:checked ~ .flex__exh display flex justify-content flex-start这样就大功告成啦,在线查看 。最后CSS 的美丽是有多个属性组合而成。切记要和设计师搞好关系,不然你这些属性都会了,做出来的东西还是会很难看的……预告:《重温 Flex 布局》正在路上。参考资料Vuetify配色方案:中国色MDN ...

April 1, 2019 · 2 min · jiezi

svg系列:1.svg基础知识 & 不一样的svg动画世界

1、 svg知识扫盲svg简介SVG(Scalable Vector Graphics),可缩放矢量图形,具有放大缩小不失真的特性,可以用来创建矢量图。SVG1.1 于 2003 年 1 月 14 日成为 W3C 推荐标准。SVG本质上是用XML语言描述的,所以它可以和DOM结构一样被CSS和JS编程控制,通过连续地改变SVG图形属性就可以创建SVG动画。SVG可用文本编辑器编辑,也可通过Adobe Illustator等专业编辑软件处理。SVG文件可单独使用,使用XML定义并包含DTD声明:<?xml version=“1.0” standalone=“no”?><!DOCTYPE svg PUBLIC “-//W3C//DTD SVG 1.1//EN” “http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns=“http://www.w3.org/2000/svg" version=“1.1”> <circle cx=“100” cy=“50” r=“40” stroke=“black” stroke-width=“2” fill=“red” /></svg>在现代浏览器中,我们可以直接在HTML代码中嵌入SVG代码:<div class=“svg-wrap”> <svg xmlns=“http://www.w3.org/2000/svg" version=“1.1”> <circle cx=“100” cy=“50” r=“40” stroke=“black” stroke-width=“2” fill=“red” /> </svg></div><svg>元素即<svg>标签,svg有三个视图相关的属性:viewport 即用x, y, width, height定义的页面矩形区域viewbox 定义用户视野的位置和大小,如 viewBox =‘20 20 100 100’ 定义了起始坐标为(20, 20),宽高为100的矩形区域,演示地址preserveAspectRatio 保持横纵比,当viewport和viewbox 长宽比不一致时,该属性可以控制2者的对齐和裁剪情况,演示地址用法<align> [<meet or slice>]align: xMid xMin xMax 和 YMid YMin YMax 自由组合 e.g. preserveAspectRatio = “xMidYMid meet"基本图形元素svg绘制基本图形元素,只要明确好各个参数的含义,应该很简单,老铁们快速过一遍:矩形,rx,ry定义圆角<rect x=“10” y=“10” width=“100” height=“100” rx=“5” ry=“5” fill=“yellow”></rect>圆 cx cy 定义圆心<circle cx=“50” cy=“50” r=“40” fill=“yellow”></circle>椭圆 rx ry 定义长轴半径、短轴半径<ellipse cx=“50” cy=“50” rx=“40” ry=“20” fill=“yellow”></ellipse>线段<line x1=“10” y1=“10” x2=“90” y2=“90” stroke=“yellow”></line>折线 点与点之间用空格隔开<polyline points=“50,10 80,90 10,30 90,30 20,90” stroke="#fb3” fill=“transparent”></polyline>多边形<polygon points=“50,10 80,90 10,30 90,30 20,90” stroke="#fb3” fill=“transparent”></polygon>路径,可以绘制上述所有图形 d属性定义路径的具体形式<path d=“M10,10 C40,5 40,140 100,100” stroke="#fb3” stroke-width=“4px”></path>文字 dx dy是相对起点坐标的偏移量,rotate指定单个文字的旋转<text x=“10” y=“10” dx=“10” dy=“10” textLength=“100” rotate=“20”>示例文字</text>其他常用标签元素主要有<g> <defs> <symbol> <use> <clipPath> <linearGradient> 等可以参考这个 codepen 演示 来理解。2、svg动画why svg?如果你问我为什么用svg做动画,而不是其他技术,那可以告诉你以下几点:svg本质上是一种图形绘制技术,广泛用于web矢量图绘制,适用于多数商业logo,卡通图片制作。因此高清屏幕使用svg可避免模糊现象。(与此相关,canvas则是用来绘制点阵图(或者说位图),两者是相对的)基于不规则path的动画效果,如路径描边(path draw),路径变体(path morph),沿路径运动(path move)等,这是目前其他技术不太容易处理的。除了path,svg也支持渐变、裁切、遮罩等特性,在其他技术遇到瓶颈时,不妨考虑svg。这里是一个用到大量svg技术的网站 welikesmall,可以参考里面的效果实现。轻量,且兼容性好。svg于2003年已成为web标准,通常几十行svg代码就可解决特定的需求。svg在未来也有着可观的发展前景。SVG + SMIL 实现动画SMIL 动画 Demo基于SMIL标准,可以直接借助svg animate标签实现动画。目前SMIL动画多数都可以用css3来代替,但要让某个物体沿着特定路径移动,css3就无能为力了(除了chrome46新增的motion-path)。遗憾的是,SMIL标准正逐渐衰落,并不会成为未来世界的主流,它正逐渐被css3所代替,所以我们只需简单了解svg中的SMIL特性即可。<set>set 元素可以在特定时间后,瞬间修改图形元素的某个属性值,它不是动画,是一种突变。<animate>animate元素定义在图形元素里面,它可以改变图形某一个属性的值,需要指定起始值和结束值(from to)以及持续时间(dur)<animateMotion>animateMotion它可以引用一个事先定义好的动画路径,用<mpath>元素引用,让图形按路径定义的方式运动。<animateTransform>类似于css transform,它可以改变图形的transform属性,e.g. rotate | translate | scale | skewX | skewYsvg + css3 或 svg + js 实现动画snap.svg 动画 Demo通过css或js修改svg图形的属性也可实现动画,这种方式也是目前最常用的。这就涉及到其他的知识点了,css3的transform,transition,animation等,js的setInterval和requestAnimationFrame。除了用原生代码控制,我们当然也可以用业界已经成熟的动画库来操作属性:anime.js (15kb左右,github 20k star, 最轻量,推荐使用)velocity.js (50kb左右,不依赖jq,号称比css动画快)snap.svg.js (svg中的jQuery,专业操作svg,80kb左右)GreenSock (100kb左右,动画功能齐全,部分插件收费)// animejs api var animation= anime({ targets: ‘#svg-path’, // 目标元素,支持css选择器,dom,dom数组等 opacity: ‘0’, 要变化的属性,这里是opacity属性,可以是任何css或svg属性 duration: 3000, delay: 1000, easing: ’easeInOutQuart’, loop: true, direction: ‘reverse’, // 动画方向:逆向播放 complete: function(anim) { }});常见的图形属性如下所示:fill 定义填充颜色fill-opacity 定义填充透明度stroke 边框颜色stroke-widthstroke-opacitystroke-linecap 单条线端点样式 butt | round | squarestroke-dasharray 定义虚线样式,间隔定义实线和空白的长度,如 10 5 5 10stroke-dashoffset 设置虚线描边偏移量,使图案向前移动opacityfont-size | font-weight | font-family | font-style | text-decoration 同CSStransform | transform-origin 同CSSrotate svg独有,定义 单个文字 的旋转角d 路径的描述属性3、相关工具网站由于svg技术过于强大,此文章只是抛砖引玉,更多精髓等待大家发掘:method draw | 在线制作svg图片svgo | 强大的svg压缩工具svg trick | 一个研究svg技术的网站vivus | 制作svg路径动画的js库snap.svg.js | svg中的jquerysnap.svg中文文档鑫空间,鑫生活 | 博主对svg和css研究很深 ...

March 28, 2019 · 2 min · jiezi

前端 CSS : 7# 纯 CSS 实现像极了爱情

介绍最近突然回想到之前看过的一个动画,是一个正方体向球体表示爱意,被拒绝,改变自己的小动画。找了半天终于找到了,个人感觉是一个很棒的动画,强烈安利:《方块》效果预览github.io 浏览源代码地址https://github.com/shanyuhai1…代码解读1. 导演上场这个动画中存在镜头转换,所以我们需要一个导演把控全局,写个简单效果进行测试。场景 love 就位,导演 director 就位,龙套们 actors 就位:<figure class=“love”> <div class=“director”> <p class=“actors”> 丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。 明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间? 转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。 </p> <p class=“actors”> 红藕香残玉簟秋。轻解罗裳,独上兰舟。云中谁寄锦书来,雁字回时,月满西楼。 花自飘零水自流。一种相思,两处闲愁。此情无计可消除,才下眉头,却上心头。 </p> </div></figure>场景过于简陋,需要美化升级一下,演员们也要稍微装饰一下:* { margin: 0; padding: 0; box-sizing: border-box;}body { position: relative; height: 100vh; width: 100vw; background-color: #f1f1f1; overflow: hidden;}.love { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /* 不定宽高垂直居中推荐 flex,此处玩玩而已 / width: 800px; height: 500px; background-color: #b5bfc0; overflow: hidden;}.director { width: 1200px; height: 100%; border: 1px dashed red; display: flex; justify-content: space-between;}.actors { width: 45%; border: 1px solid purple;}镜头 len 准备,导演试机:.director { animation: len 5s linear .2s infinite;}@keyframes len { from { transform: translateX(0); } to { transform: translateX(-400px); }}测试完成。2. 猪脚 cube 登场龙套们退场,猪脚登场。猪脚要有玉树凌风之姿,一米八的身高配上微微侧身,先来一张猪脚的海报吸引人气吧:<div class=“director”> <header> <h1 class=“title”>Cube</h1> </header> <section class=“cube”> <div class=“cube__body”> <div class=“cube__face cube__face_front”>(❁´▽`❁)</div> <div class=“cube__face cube__face_back”></div> <div class=“cube__face cube__face_left”></div> <div class=“cube__face cube__face_right”></div> <div class=“cube__face cube__face_top”></div> <div class=“cube__face cube__face_bottom”></div> </div> </section></div>h1 { position: absolute; left: 100px; top: 100px; font-size: 140px; letter-spacing: 6px; color: #fff; user-select: none; text-shadow: 0 0 5px rgba(0, 0, 0, .05), 0 1px 3px rgba(0, 0, 0, .2), 0 3px 5px rgba(0, 0, 0, .2), 0 5px 10px rgba(0, 0, 0, .2), 0 10px 10px rgba(0, 0, 0, .2), 0 20px 20px rgba(0, 0, 0, .3);}.cube { position: absolute; left: 540px; –size: 180px; width: var(–size); height: var(–size); perspective: 600px;}.cube__body { position: relative; width: inherit; height: inherit; transform-style: preserve-3d; transform: rotateY(15deg);}.cube__face { position: absolute; width: inherit; height: inherit; opacity: .5; display: flex; align-items: center; justify-content: center; color: black; background-color: lightcyan; border: 2px solid #fff; box-shadow: inset 0 0 15px rgba(0, 0, 0, .5);}.cube__face_front { transform: rotateY(0) translate3d(0, 0, calc(var(–size) / 2));}.cube__face_back { transform: rotateY(180deg) translate3d(0, 0, calc(var(–size) / 2));}.cube__face_left { transform: rotateY(-90deg) translate3d(0, 0, calc(var(–size) / 2));}.cube__face_right { transform: rotateY(90deg) translate3d(0, 0, calc(var(–size) / 2));}.cube__face_top { transform: rotateX(90deg) translate3d(0, 0, calc(var(–size) / 2));}.cube__face_bottom { transform: rotateX(-90deg) translate3d(0, 0, calc(var(–size) / 2));}猪脚表示它并不想吊威亚,那就放它下来吧,试镜发现化的妆也浓了,这张海报不合格:.love { display: flex; align-items: flex-end; padding-bottom: 80px;}.cube__face { box-shadow: inset 0 0 2px rgba(0, 0, 0, .5);}重新拍一张海报,用来宣传:这样猪脚看起来还是眉清目秀的。3. 替身救场原本是计划着猪脚两个前滚翻进入下一场景,奈何缺乏动作指导无法实现(求一个动作指导帮帮猪脚),这时候就只能靠替身救场了。猪脚的前滚翻:.cube__body { animation: woo 2s linear forwards;}@keyframes woo { 0% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(0); } 5% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(0); } 50% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(.25turn); } 99% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(.25turn); } 100% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(.25turn); visibility: hidden; }}替身接替前滚翻:<section class=“substitute”> <div class=“substitute__body”> <div class=“cube__face cube__face_front”>替身</div> <div class=“cube__face cube__face_back”></div> <div class=“cube__face cube__face_left”></div> <div class=“cube__face cube__face_right”></div> <div class=“cube__face cube__face_top”></div> <div class=“cube__face cube__face_bottom”></div> </div></section>.substitute__body { animation: woo_sub 2s 1.85s linear forwards; visibility: hidden;}@keyframes woo_sub { 1% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(0); visibility: hidden; } 5% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(0); visibility: hidden; } 50% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(.25turn); visibility: visible; } 100% { transform-origin: 100% 100%; transform: rotateY(0) rotateZ(.25turn); visibility: visible; }}镜头准备,再一次试镜:场景长度不够,等下配角们站哪里?赶紧加点预算:.director { width: 1700px;}@keyframes len { from { transform: translateX(0); } to { transform: translateX(-900px); }}4. 配角 sphere 上场由于导演全局把控能力太差,所以需要分镜头拍摄:.director { / animation: len 6s linear .2s forwards; / transform: translateX(-900px);}.cube__body { / animation: woo 2s 1s linear forwards; /}.substitute__body { / animation: woo_sub 2s 2.85s linear forwards; */}配角们身高一米六,化好妆后登场:<section class=“sphere__wrap”> <div class=“sphere sphere_left”></div> <div class=“sphere sphere_right”></div></section>.sphere__wrap { position: absolute; bottom: 0; right: -840px; height: 320px; width: 450px; display: flex; align-items: flex-end; justify-content: space-between;}.sphere { –sphere-size: 160px; width: var(–sphere-size); height: var(–sphere-size); border-radius:50%; box-shadow: 0 3em 2.5em -2.5em rgba(53,64,73,.6), 0 0 1em -0.35em rgba(255,255,255,.2), 0 -1em 1.1em 0 rgba(255,255,255,.5) inset, 0 0 2.5em 0 rgba(227,249,250,.4) inset; background: radial-gradient(circle at 33% -25%,rgba(227,249,250,0) 40%,rgba(0, 0, 0,.07) 50%), radial-gradient(circle at 50% 135%, rgba(0, 0, 0,.23) 43%,rgba(227,249,250,0)), radial-gradient(circle at 50% -35%, rgba(227,249,250,.8) 45%,rgba(227,249,250,0)), radial-gradient(circle at 50% 0, #fff,lightcyan); background-size: 150%, 100%, 100%, 100%;}喂,身为一个演员动作要丰富,不是让你站着当花瓶的:.sphere_left { animation: move 1s ease-in forwards;}@keyframes move { to { transform: translateX(50px) rotate(.05turn); }}还要有丰富的感情,你以为是小鲜肉面无表情就可以吗:.love { –color-love: #ed5a65;}.sphere_left::after { content: “❤”; font-size: 42px; color: var(–color-love); position: absolute; top: 0; right: 0; transform: scale(0); animation: love_gen 1s .8s linear forwards;}@keyframes love_gen { from { transform: scale(0) translate(0, 0); } to { transform: scale(1) translate(25px, -40px) rotate(-.05turn); }}OK,试镜:看起来还不错,下一个镜头。5. 猪脚示爱配角们的镜头拍完了,还需要补拍猪脚示爱的镜头,这时候就要展现猪脚炸裂的演技了,从示爱到心碎:.substitute__body::after { content: “❤”; font-size: 30px; color: var(–color-love); position: absolute; top: 0; right: 0; transform: scale(0); animation: love_vip 5s 4s linear forwards;}@keyframes love_vip { 0% { transform: scale(0) translate(-100px, -200px); } 10%, 80% { transform: scale(1) translate(-125px, -110px) rotate(-.25turn); } 100% { content: “????”; transform: scale(1) translate(-125px, -110px) rotate(-.25turn); }}试镜:敬业的猪脚对这个效果不满意,要求重拍:<span class=“heart” data-text="❤">❤</span>.heart { font-size: 40px; color: transparent; position: absolute; top: 0; left: 0; user-select: none; transform: translate(50px, -50px) rotate(-.25turn) scale(0); animation: love_vip 5s 4s linear forwards;}.heart::before,.heart::after { position: absolute; content: attr(data-text); top: 0; left: 0; color: var(–color-love);}.heart::before { clip-path: polygon(0 0, 60% 0, 30% 100%, 0 100%); animation: love_broken_left 1s 8s linear forwards;}.heart::after { clip-path: polygon(60% 0, 100% 0, 100% 100%, 30% 100%); animation: love_broken_right 1s 8s linear forwards;}@keyframes love_vip { 0% { transform: scale(0) translate(50px, -50px) rotate(-.25turn); } 20%, 80% { transform: scale(1) translate(10px, -125px) rotate(-.25turn); } 100% { transform: scale(1) translate(30px, -125px) rotate(-.25turn); }}@keyframes love_broken_left { to { left: -.15em; transform: rotate(-5deg); top: -0.05em; }}@keyframes love_broken_right { to { left: .15em; transform: rotate(5deg); top: 0.05em; }}试镜:好了,这一幕可以过了。6. 猪脚失恋导演:你要充分表现这个失恋的情绪:哀伤、自闭、最后化成一堆碎片……猪脚:我是谁,我在哪……旁白:敬业的猪脚因完成不了导演的要求,选择离开潜心学习表演技巧,并表示因为自己演技不够所以不要这份报酬了……7. 预告版投资商作为猪脚的亲爹,表示相信猪脚一定可以锻炼好演技,于是请了百万后期来修整一下片子作为预告版吸引人气,并宣称延期发布正式版(预告版请上翻到效果预览处),这一部分过多修改,具体请看源代码地址。最后友情提示:CSS 虽然很有意思,但还是推荐重心放在 JavaScript 上。参考资料取色 ...

March 28, 2019 · 4 min · jiezi

11 个最好的 JavaScript 动态效果库

翻译:疯狂的技术宅原文:https://blog.bitsrc.io/11-jav…当我想要在网上找一个简洁的 Javascript 动效库时,总是发现很多“推荐”的库都是缺乏持续维护的。经过一番研究,我收集了 11 个最好的库,你可以用在自己的项目中。另外我还添加了一些有用但是缺少持续维护的库。提示:可以用 Bit 来共享你的组件,用它们构建多个项目并与你的团队同步更改。不要重复造轮子。这里有一些 React spinners:上面的每个组件都可以在站点上找到并测试,可以直接用在自己的项目中。使用纯CSS在深入研究这些库之前,不要忘记还有纯 CSS。为什么?因为它是标准的,可以提高性能(GPU),能够提供很好的向后和向前兼容性,它可能是创建动态效果的最有效方式。1. Three.js得到超过 43K 的star,这个流行的库是在浏览器上创建 3D 动画的最好的一种方式,它用了 WebGL 。通过提供<canvas>、<svg>、CSS3D 和 WebGL 渲染器,该库可以让我们跨设备和浏览器创建丰富的交互式体验。该库于 2010 年 4 月首次推出,目前仍有近 1000 名参与者开发。github: https://github.com/mrdoob/thr...2. Anime.js超过20K的 star,Anime 是一个 JavaScript 动画库,可以处理 CSS 属性、单个 CSS 转换、SVG或任何DOM属性,以及 JavaScript 对象。这个库允许你链接多个动画属性、对多个实例进行同步、创建时间轴等。github:https://github.com/juliangarn…3. Mo.js这个库有 14K 的 star,是一个用于 Web 的动态图形工具集,有简单的声明性 API、跨设备兼容性和超过 1500 个单元测试。你可以在 DOME 或 SVG DOME 周围移动你创建的图形或创建唯一的 mo.js 对象。尽管文档有些欠缺,但是示例还是很丰富的,这里有mo.js 的 CSS 技巧的介绍。github:https://github.com/legomushro…4. Velocitycodepen上的演示:https://codepen.io/hone/pen/a…有 15K 的 star,Velocity 是一个快速的 Javascript 动画引擎,有与jQuery的 $.animate(). 相同的API。它具有颜色动画、变换、循环、渐变、SVG支持和滚动。这是 Velocity 高性能引擎的细分,这是使用该库的SVG动画的简介。github:https://github.com/julianshap…5. Popmotion有 14K 的star,这个库只有 11kb 的大小。它允许开发人员从动作创建动画和交互,动作是可以启动和停止的值的流,并使用CSS、SVG、React、three.js 和任何接受数字作为输入的 API 创建。github:https://github.com/Popmotion/…6. Vivus超过10K的star,Vivus 是一个零依赖 JavaScript 类,可以让你制作 SVG 动画,让它们具有能够被绘制的外观。有很多动画模版可以用,也可以创建自定义脚本来绘制 SVG。不妨亲自去 Vivus-instant 看一看实例。github:https://github.com/maxwellito…7. GreenSock JSGSAP 是一个 JavaScript 库,用于创建高性能、零依赖性、跨浏览器的动画,其声称有超过400万个网站在使用。 GSAP 非常灵活,可以与React、Vue、Angular 和 原生 JS 一起使用。 GSDevtools 可以帮助你修改用 GSAP 构建的动画。github:https://github.com/greensock/…8. Scroll Reveal凭借15K 的star 和零依赖关系,该库为 Web 和移动浏览器提供了简单的滚动动画,能够以动画的方式显示滚动内容。它支持多种简洁的效果,甚至可以让你使用自然语言去定义动画。这是一个简短的SitePoint教程。github:https://github.com/jlmakes/sc…9. Hover (CSS)嗯,这是一个 CSS 库。获得了 20K 的 star,Hover 提供了 CSS3 的强大悬停效果,可用于链接、按钮、徽标、SVG 和图像等,能在CSS、Sass和LESS中使用。你可以找到要在自己的样式表中使用的效果并简单的复制粘贴,或者去引用样式表。github:https://github.com/IanLunn/Hover10. Kute.js一个完全成熟的原生 JavaScript 动画引擎,具有跨浏览器动画的基本功能。不过重点还是代码质量、灵活性、性能和大小(其核心引擎只有17k 大小,压缩包仅 5.5k)。这里是演示。该库也是可扩展的,因此你可以添加自己的功能。github:https://github.com/thednp/kut…11. Typed.js这个有6K star 的库的基本功能是能让你以选定的速度创建一个打字动画。你还可以在页面上放置一个用户不可见的 <div> 标签并从中读取你要输出的内容,并能方便搜索引擎抓取。这个库非常流行而且充满新意。github:https://github.com/luisvinici…另外还有iTyped:https://github.com/luisvinici…有用但缺乏维护的注意,这 8 个库大多没有维护,所以请小心使用。Particles —— 用于创建粒子的轻量级JavaScript库github:https://github.com/VincentGar…loaders.css —— 令人愉快的,以性能为中心的纯 css 加载动画。github:https://github.com/ConnorAthe…Parallax JS —— 对智能设备的方向作出反应的视差引擎github:https://github.com/wagerfield…Bounce.js —— 快速创建漂亮的 CSS3 动画。github.com:https://github.com/tictail/bo…CTA JS —— 使你的“动作效果”路径动起来github:https://github.com/chinchang/…html5tooltipsjs - 流畅的 3D 动画提示效果github.com:https://github.com/ytiurin/ht…Pace JS —— 自动为你的网站添加进度条。 github:https://github.com/HubSpot/paceAnijs —— 无需编码即可提升网页设计效果的库。github:https://github.com/anijs/anijs本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从7个开放式的前端面试题React 教程:快速上手指南 ...

March 19, 2019 · 1 min · jiezi

手把手教你实现一个引导动画

本文由云+社区发表作者:陈纪庚前言最近看了一些文章,知道了实现引导动画的基本原理,所以决定来自己亲手做一个通用的引导动画类。我们先来看一下具体的效果:点这里原理通过维护一个Modal实例,使用Modal的mask来隐藏掉页面的其他元素。根据用户传入的需要引导的元素列表,依次来展示元素。展示元素的原理:通过cloneNode来复制一个当前要展示元素的副本,通过当前元素的位置信息来展示副本,并且通过z-index属性来让其在ModalMask上方展示。大致代码如下: const newEle = target.cloneNode(true); const rect = target.getBoundingClientRect(); newEle.style.zIndex = ‘1001’; newEle.style.position = ‘fixed’; newEle.style.width = ${rect.width}px; newEle.style.height = ${rect.height}px; newEle.style.left = ${rect.left}px; newEle.style.top = ${rect.top}px; this.modal.appendChild(newEle);当用户点击了当前展示的元素时,则展示下一个元素。原理听起来是不是很简单?但是其实真正实现起来,还是有坑的。比如说,当需要展示的元素不在页面的可视范围内如何处理。当要展示的元素不在页面可视范围内,主要分为三种情况:展示的元素在页面可视范围的上边。展示的元素在页面可视范围的下边。展示的元素在可视范围内,可是展示不全。由于我是通过getBoundingClientRect这个api来获取元素的位置、大小信息的。这个api获取的位置信息是相对于视口左上角位置的(如下图)。对于第一种情况,这个api获取的top值为负值,这个就比较好处理,直接调用window.scrollBy(0, rect.top)来将页面滚动到展示元素的顶部即可。而对于第二、三种情况,我们可以看下图从图片我们可以看出来,当rect.top+rect.height < window.innerHeight的时候,说明展示的元素不在视野范围内,或者展示不全。对于这种情况,我们也可以通过调用window.scrollBy(0, rect.top)的方式来让展示元素尽可能在顶部。对上述情况的调节代码如下:// 若引导的元素不在页面范围内,则滚动页面到引导元素的视野范围内adapteView(ele) { const rect = ele.getBoundingClientRect(); const height = window.innerHeight; if (rect.top < 0 || rect.top + rect.height > height) { window.scrollBy(0, rect.top); }}接下来,我们就来一起实现下这个引导动画类。第一步:实现Modal功能我们先不管具体的展示逻辑实现,我们先实现一个简单的Modal功能。class Guidences { constructor() { this.modal = null; this.eleList = []; } // 入口函数 showGuidences(eleList = []) { // 允许传入单个元素 this.eleList = eleList instanceof Array ? eleList : [eleList]; // 若之前已经创建一个Modal实例,则不重复创建 this.modal || this.createModel(); } // 创建一个Modal实例 createModel() { const modalContainer = document.createElement(‘div’); const modalMask = document.createElement(‘div’); this.setMaskStyle(modalMask); modalContainer.style.display = ’none’; modalContainer.appendChild(modalMask); document.body.appendChild(modalContainer); this.modal = modalContainer; } setMaskStyle(ele) { ele.style.zIndex = ‘1000’; ele.style.background = ‘rgba(0, 0, 0, 0.8)’; ele.style.position = ‘fixed’; ele.style.top = 0; ele.style.right = 0; ele.style.bottom = 0; ele.style.left = 0; } hideModal() { this.modal.style.display = ’none’; this.modal.removeChild(this.modalBody); this.modalBody = null; } showModal() { this.modal.style.display = ‘block’; }}第二步:实现展示引导元素的功能复制一个要展示元素的副本,根据要展示元素的位置信息来放置该副本,并且将副本当成Modal的主体内容展示。class Guidences { constructor() { this.modal = null; this.eleList = []; } // 允许传入单个元素 showGuidences(eleList = []) { this.eleList = eleList instanceof Array ? eleList : [eleList]; this.modal || this.createModel(); this.showGuidence(); } // 展示引导页面 showGuidence() { if (!this.eleList.length) { return this.hideModal(); } // 移除上一次的展示元素 this.modalBody && this.modal.removeChild(this.modalBody); const ele = this.eleList.shift(); // 当前要展示的元素 const newEle = ele.cloneNode(true); // 复制副本 this.modalBody = newEle; this.initModalBody(ele); this.showModal(); } createModel() { // … } setMaskStyle(ele) { // … } initModalBody(target) { this.adapteView(target); const rect = target.getBoundingClientRect(); this.modalBody.style.zIndex = ‘1001’; this.modalBody.style.position = ‘fixed’; this.modalBody.style.width = ${rect.width}px; this.modalBody.style.height = ${rect.height}px; this.modalBody.style.left = ${rect.left}px; this.modalBody.style.top = ${rect.top}px; this.modal.appendChild(this.modalBody); // 当用户点击引导元素,则展示下一个要引导的元素 this.modalBody.addEventListener(‘click’, () => { this.showGuidence(this.eleList); }); } // 若引导的元素不在页面范围内,则滚动页面到引导元素的视野范围内 adapteView(ele) { const rect = ele.getBoundingClientRect(); const height = window.innerHeight; if (rect.top < 0 || rect.top + rect.height > height) { window.scrollBy(0, rect.top); } } hideModal() { // … } showModal() { // … }}完整的代码可以在点击这里调用方式const guidences = new Guidences();function showGuidences() { const eles = Array.from(document.querySelectorAll(’.demo’)); guidences.showGuidences(eles);}showGuidences();总结除了使用cloneNode的形式来实现引导动画外,还可以使用box-shadow、canvas等方式来做。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

March 13, 2019 · 2 min · jiezi

原生 js 实现一个有动画效果的进度条插件 progress

效果图:项目地址:https://github.com/biaochenxuying/progress效果体验地址: https://biaochenxuying.github.io/progress/index.html1. 原理一个用于装载进度条内容的 div (且叫做 container)。然后在 container 里面动态生成三个元素,一个是做为背景的 div (且叫做 progress),一个是做为显示进度的 div (且叫做 bar),还有一个是显示文字的 span (且叫做 text)。progress 的宽为 100%,bar 的宽根据传入数值 target 的值来定( 默认为 0 ,全部占满的值为 100 ),text 展示的文字为 bar 的宽占 progress 宽的百分比。bar 的宽从 0 逐渐增加到的 target 值的过程( 比如: 0 > 80 ),给这个过程添加一个逐渐加快的动画。2. 代码实现具体的过程请看代码:/** author: https://github.com/biaochenxuying*/(function() { function Progress() { this.mountedId = null; this.target = 100; this.step = 1; this.color = ‘#333’; this.fontSize = ‘18px’; this.borderRadius = 0; this.backgroundColor = ‘#eee’; this.barBackgroundColor = ‘#26a2ff’; } Progress.prototype = { init: function(config) { if (!config.mountedId) { alert(‘请输入挂载节点的 id’); return; } this.mountedId = config.mountedId; this.target = config.target || this.target; this.step = config.step || this.step; this.fontSize = config.fontSize || this.fontSize; this.color = config.color || this.color; this.borderRadius = config.borderRadius || this.borderRadius; this.backgroundColor = config.backgroundColor || this.backgroundColor; this.barBackgroundColor = config.barBackgroundColor || this.barBackgroundColor; var box = document.querySelector(this.mountedId); var width = box.offsetWidth; var height = box.offsetHeight; var progress = document.createElement(‘div’); progress.style.position = ‘absolute’; progress.style.height = height + ‘px’; progress.style.width = width + ‘px’; progress.style.borderRadius = this.borderRadius; progress.style.backgroundColor = this.backgroundColor; var bar = document.createElement(‘div’); bar.style.float = ’left’; bar.style.height = ‘100%’; bar.style.width = ‘0’; bar.style.lineHeight = height + ‘px’; bar.style.textAlign = ‘center’; bar.style.borderRadius = this.borderRadius; bar.style.backgroundColor = this.barBackgroundColor; var text = document.createElement(‘span’); text.style.position = ‘absolute’; text.style.top = ‘0’; text.style.left = ‘0’; text.style.height = height + ‘px’; text.style.lineHeight = height + ‘px’; text.style.fontSize = this.fontSize; text.style.color = this.color; progress.appendChild(bar); progress.appendChild(text); box.appendChild(progress); this.run(progress, bar, text, this.target, this.step); }, /** * @name 执行动画 * @param progress 底部的 dom 对象 * @param bar 占比的 dom 对象 * @param text 文字的 dom 对象 * @param target 目标值( Number ) * @param step 动画步长( Number ) */ run: function(progress, bar, text, target, step) { var self = this; ++step; var endRate = parseInt(target) - parseInt(bar.style.width); if (endRate <= step) { step = endRate; } var width = parseInt(bar.style.width); var endWidth = width + step + ‘%’; bar.style.width = endWidth; text.innerHTML = endWidth; if (width >= 94) { text.style.left = ‘94%’; } else { text.style.left = width + 1 + ‘%’; } if (width === target) { clearTimeout(timeout); return; } var timeout = setTimeout(function() { self.run(progress, bar, text, target, step); }, 30); }, }; // 注册到 window 全局 window.Progress = Progress;})();3. 使用方法 var config = { mountedId: ‘#bar’, target: 8, step: 1, color: ‘green’, fontSize: “20px”, borderRadius: “5px”, backgroundColor: ‘#eee’, barBackgroundColor: ‘red’, }; var p = new Progress(); p.init(config);4. 最后笔者的博客后花圆地址如下:github :https://github.com/biaochenxuying/blog个人网站 :http://biaochenxuying.cn/main.html如果您觉得这篇文章不错或者对你有所帮助,请给个赞呗,你的点赞就是对我最大的鼓励,谢谢。微信公众号:BiaoChenXuYing分享 前端、后端开发等相关的技术文章,热点资源,随想随感,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源分享–Python、Java、Linux、Go、node、vue、react、javaScript ...

February 26, 2019 · 2 min · jiezi

学习 PixiJS — 补间动画

说明补间动画指的是,我们可以通过为精灵的位置、比例、透明度,等属性,设置开始值和结束值,制作动画,动画中间需要的部分由软件自动计算填充。Pixi 没有内置补间引擎,但是你可以使用很多很好的开源的补间库,比如 Tween.js 和 Dynamic.js 。如果要制作非常专业的自定义补间效果,可以使用这两个库中的其中一个。但是现在我们要使用的是一个名为 Charm.js 的专门用于 Pixi 的补间库。使用 Charm 库要开始使用 Charm ,首先直接用 script 标签,引入 js 文件<script src=“https://www.kkkk1000.com/js/Charm.js"></script>然后创建它的实例let c = new Charm(PIXI);变量 c 现在代表 Charm 实例。和前面的文章中讲到的粒子效果一样,在调用 state 函数之后,必须为游戏循环中的每个帧更新补间。就是在游戏循环中调用 Charm 的 update 方法,如下所示:function gameLoop() { requestAnimationFrame(gameLoop); state(); c.update(); renderer.render(stage);}滑动补间Charm 最有用的补间效果之一是 slide 。使用 slide 方法可以使精灵从画布上的当前位置平滑移动到任何其他位置。slide 方法有七个参数,但只有前三个参数是必需的。名称默认值描述anySprite 需要移动的精灵finalXPosition 滑动结束时 x 坐标finalYPosition 滑动结束时 y 坐标durationInFrames60补间需要的帧数,也就是动画应该持续多长时间easingType"smoothstep"缓动类型yoyofalse用于确定精灵是否应在补间的起点和终点之间来回移动。delayTimeBeforeRepeat0一个以毫秒为单位的数字,用于确定精灵 yoyo 之前的延迟时间。示例:以下是如何使用 slide 方法使精灵用120帧从原始位置移动到坐标为(128,128)的位置的关键代码。c.slide(sprite, 128, 128, 120);效果图:查看示例如果你想让精灵在起点和终点之间来回移动,请将 yoyo(第六个参数)设置为 true,代码如下所示:c.slide(sprite, 128, 128, 120, “smoothstep”, true);查看示例补间对象Charm 所有的补间方法都返回一个补间对象,你可以这样创建:let slidePixie = c.slide(sprite, 80, 128, 120, “smoothstep”,true);slidePixie 就是补间对象,它包含一些有用的属性和方法,可以用于控制补间。其中一个是 onComplete 方法,它将在补间完成后立即运行。以下代码是精灵到达终点时如何使用 onComplete 方法在控制台中显示消息。let slidePixie = c.slide(sprite, 80, 128, 120, “smoothstep”,true);slidePixie.onComplete = () => console.log(“一次滑动完成”);如果将 yoyo (slide 方法的第六个参数)设置为 true,则每当精灵到达其起点和终点时,onComplete 方法都将运行。补间还有 pause 和 play 方法,可以停止和开始补间。slidePixie.pause();slidePixie.play();补间对象还具有 playing 属性,如果补间当前正在播放,则该属性值为 true。只不过有些补间方法返回的对象中直接有 playing 属性,有些补间方法返回的对象中的 playing 属性是在一个叫 tweens 的数组中, tweens 数组中包括了这个补间方法创建的所有补间对象。以 slide 方法为例,完成一个滑动需要创建 x 轴补间对象和 y 轴补间对象,这两个对象都放在了 tweens 数组中,这两个对象也都分别有 playing 属性。查看示例所有 Charm 的补间方法都返回你可以控制和访问的补间对象。设置缓动类型slide 方法的第四个参数是 easingType 。它是一个字符串,用于确定补间加速和减速的类型。这些类型共有15种可供选择,并且它们对于 Charm 的所有不同补间方法都是相同的。某些类型对应的会有一个基本类型,一个 squared 类型和一个cubed 类型。squared 类型和 cubed 类型只是将基本类型的效果放大而已。大多数 Charm 的补间效果的默认缓动类型是 smoothstep。Linear:linear,精灵从开始到停止保持匀速运动。Smoothstep:smoothstep,smoothstepSquared,smoothstepCubed。加速精灵并以非常自然的方式减慢速度Acceleration:acceleration, accelerationCubed。逐渐加速精灵并突然停止。如果要更加平滑的加速效果,请使用 sine,sineSquared 或 sineCubed。Deceleration:deceleration,decelerationCubed。突然加速精灵并逐渐减慢它。要获得更加平滑的减速效果,请使用inverseSine,inverseSineSquared或inverseSineCubed。Bounce:bounce 10 -10 ,这将使精灵到达起点和终点时略微反弹,更改乘数10和 -10,可以改变效果。查看示例使用 slide 进行场景过渡你在游戏或应用程序中肯定要做的一件事就是让场景过渡,然后将新场景滑入视图。它可能是你游戏的标题滑动以显示游戏的第一级,或者可能是一个菜单,可以滑动以显示更多的应用程序内容。你可以使用 slide 方法执行此操作。首先,创建两个容器对象:sceneOne 和 sceneTwo,并将它们添加到舞台上。sceneOne = new Container();sceneTwo = new Container();stage.addChild(sceneOne);stage.addChild(sceneTwo);接下来,为每个场景创建精灵。制作一个像画布一样大的蓝色矩形; 并在矩形中间添加上 Scene One 的文字,将两者都添加到 sceneOne 容器中。再制作一个像画布一样大的红色矩形;并在矩形中间添加上Scene Two 的文字,将这两者添加到 sceneTwo 容器中。你最终得到的两个容器对象,如下图所示。以下是关键代码://1. Scene one sprites: //画蓝色矩形let blueRectangle = new PIXI.Graphics();blueRectangle.beginFill(0x014ACA);blueRectangle.drawRect(0, 0, canvasWith, canvasHeight);blueRectangle.endFill();sceneOne.addChild(blueRectangle);//添加文字,并在容器中居中let sceneOneText = new PIXI.Text(“Scene One”);sceneOneText.style = { fill: “#fff”, fontSize: “40px” };let sceneOneTextX = (canvasWith - sceneOneText.width) / 2;let sceneOneTextY = (canvasWith - sceneOneText.height) / 2;sceneOneText.position.set(sceneOneTextX, sceneOneTextY);sceneOne.addChild(sceneOneText);//2. Scene two sprites://画红色矩形let redRectangle = new PIXI.Graphics();redRectangle.beginFill(0xEF4631);redRectangle.drawRect(0, 0, canvasWith, canvasHeight);redRectangle.endFill();sceneTwo.addChild(redRectangle);//添加文字,并在容器中居中let sceneTwoText = new PIXI.Text(“Scene Two”);sceneTwoText.style = { fill: “#fff”, fontSize: “40px” };let sceneTwoTextX = (canvasWith - sceneTwoText.width) / 2;let sceneTwoTextY = (canvasHeight - sceneTwoText.height) / 2;sceneTwoText.position.set(sceneTwoTextX, sceneTwoTextY);sceneTwo.addChild(sceneTwoText);在一个真实的项目中,你可以为每个容器填充每个场景所需的精灵数量,你也可以为你的项目添加尽可能多的场景容器。接下来,将 sceneTwo 移开,使其位于画布的右边缘之外。代码如下所示:sceneTwo.x = canvasWith;这将在画布上显示 sceneOne,而 sceneTwo 在需要时会从左侧滑出,如下所示。sceneTwo 就在屏幕外等着。最后,使用 slide 方法从 sceneOne 过渡到 sceneTwo 。只需将 sceneOne 滑动到左侧,然后从右侧滑动 sceneTwo ,取代它的位置,代码如下。c.slide(sceneTwo, 0, 0);c.slide(sceneOne, -canvasWith, 0);下图显示了这段代码的效果。查看示例时间过渡你可以自定义一个 wait 函数在设定的时间间隔后进行过渡。function wait(duration = 0) { return new Promise((resolve, reject) => { setTimeout(resolve, duration); });}要使用 wait,请为其提供一个参数,它代表你希望等待的时间(以毫秒为单位)。以下是在延迟1秒(1000毫秒)后从 sceneOne 过渡到 sceneTwo 的方法。wait(1000).then(() => { c.slide(sceneTwo, 0, 0); c.slide(sceneOne, -canvasWith, 0);});查看示例其实在 Charm 库中已经定义了 wait 这个方法,原理和上面的 wait 函数是一样的。你可以这样使用它。c.wait(1000).then(() => { c.slide(sceneTwo, 0, 0); c.slide(sceneOne, -canvasWith, 0);});沿贝塞尔曲线移动如果你还不清楚什么是贝塞尔曲线,可以先看看这篇文章。slide 方法沿直线为精灵制作动画,但你也可以使用另一种方法(followCurve)使精灵沿贝塞尔曲线移动。首先,将贝塞尔曲线定义为4个坐标点的二维数组,如下所示:let curve = [ [sprite.x, sprite.y], //起始点 [108, 32], //控制点1 [176, 32], //控制点2 [196, 160] //结束点];followCurve 方法的参数如下:名称默认值描述anySprite 需要移动的精灵curve 贝塞尔曲线数组durationInFrames60补间需要的帧数,也就是动画应该持续多长时间easingType"smoothstep"缓动类型yoyofalse用于确定精灵是否应在补间的起点和终点之间来回移动。delayTimeBeforeRepeat0一个以毫秒为单位的数字,用于确定精灵 yoyo 之前的延迟时间。接下来,使用 Charm 的 followCurve 方法使精灵跟随该曲线。(提供 curve 数组作为第二个参数)c.followCurve( sprite, //需要移动的精灵 curve, //贝塞尔曲线数组 120, //持续时间,以帧为单位 “smoothstep”, //缓动类型 true, //yoyo 1000 //yoyo之前的延迟时间);效果图:如果你需要使精灵的中点沿着曲线移动,还需要设置精灵的锚点(anchor)居中,如下所示:sprite.anchor.set(0.5, 0.5);查看示例slide 和 followCurve 方法适用于简单的来回动画效果,但你也可以结合它们以使精灵遵循更复杂的路径。沿路径移动你可以使用 Charm 的 walkPath 方法连接一系列点,并使精灵移动到每个点。该系列中的每个点都称为 waypoint 。首先,从由坐标点组成的二维数组定位路径点开始,这些 waypoint 映射出你希望精灵遵循的路径。let waypoints = [ [32, 32], //要移动到的第一个坐标点 [32, 128], //要移动到的第二个坐标点 [300, 128], //要移动到的第三个坐标点 [300, 32], //要移动到的第四个坐标点 [32, 32] //要移动到的第五个坐标点];你可以根据需要使用任意多的 waypoint。walkPath 方法的参数如下:名称默认值描述anySprite 需要移动的精灵waypoints 坐标点的二维数组durationInFrames60补间需要的帧数,也就是动画应该持续多长时间easingType"smoothstep"缓动类型loopfalse用于确定精灵在到达结尾时是否从头开始yoyofalse用于确定精灵是否应在补间的起点和终点之间来回移动。delayBetweenSections0一个以毫秒为单位的数字,用于确定精灵在移动到路径的下一部分之前应该等待的时间。接下来,使用 walkPath 方法使精灵按顺序移动到所有这些点。(前两个参数是必需的)c.walkPath( sprite, //需要移动的精灵 waypoints, //坐标点的二维数组 300, //持续时间,以帧为单位 “smoothstep”, //缓动类型 true, //循环 true, //轮流反向播放动画 1000 //移动到路径的下一部分之前应该等待的时间);效果图:查看示例而使用 walkCurve 方法,可以使精灵遵循一系列连接的贝塞尔曲线。首先,创建任何贝塞尔曲线数组,描述你希望精灵遵循的路径。let curvedWaypoints = [ //第一条曲线 [[sprite.x, sprite.y],[75, 500],[200, 500],[300, 300]], //第二条曲线 [[300, 300],[250, 100],[100, 100],[sprite.x, sprite.y]]];每条曲线的四个点与 followCurve 方法中的相同:起始位置,控制点1,控制点2和结束位置。第一条曲线中的最后一个点应与下一条曲线中的第一个点相同。你可以根据需要使用尽可能多的曲线。walkCurve 方法的参数如下:名称默认值描述anySprite 需要移动的精灵curvedWaypoints 贝塞尔曲线的坐标点的数组durationInFrames60补间需要的帧数,也就是动画应该持续多长时间easingType"smoothstep"缓动类型yoyofalse用于确定精灵是否应在补间的起点和终点之间来回移动。delayBeforeContinue0一个以毫秒为单位的数字,用于确定精灵yoyo之前的延迟时间。接下来,提供 curvedWaypoints 数组作为 walkCurve 方法中的第二个参数,来试试这个方法。c.walkCurve( sprite, //需要移动的精灵 curvedWaypoints, //贝塞尔曲线的坐标点的数组 300, //持续时间,以帧为单位 “smoothstep”, //缓动类型 true, //循环 true, //轮流反向播放动画 1000 //移动到路径的下一部分之前应该等待的时间);效果图:查看示例使用 walkPath 和 walkCurve 将为你提供了一个很好的开端,它们可以为游戏制作一些有趣的动画。更多补间效果Charm 有许多其他内置的补间效果,你会发现它们在游戏和应用程序中有很多用处。下面是其他一些效果的介绍。fadeOut 和 fadeInfadeOut 方法使精灵逐渐变得透明,fadeIn 方法使精灵从透明逐渐显现。这两个方法需要的参数是一样的。参数:名称默认值描述anySprite 需要产生效果的精灵durationInFrames60持续的帧数示例:c.fadeOut(anySprite);c.fadeIn(anySprite);查看示例pulse使用 pulse 方法可以使精灵以稳定的速率连续淡入淡出。参数:名称默认值描述anySprite 需要产生效果的精灵durationInFrames60淡入淡出应该持续的帧数,也就是持续时间minAlpha0精灵可以减少到的最小的透明度值示例:c.pulse(anySprite);查看示例如果你只希望精灵在再次淡入之前变为半透明,请将第三个参数设置为0.5,如下所示:c.pulse(anySprite, 60, 0.5);scale你可以使用 scale 方法让精灵产生缩放效果。参数:名称默认值描述anySprite 需要产生效果的精灵endScaleX0.5x 轴缩放的比例endScaleY0.5y 轴缩放的比例durationInFrames60持续时间,以帧为单位示例:c.scale( sprite, //精灵 0.1, //x轴缩放的比例 0.1, //y轴缩放的比例 100 //持续时间,以帧为单位);查看示例breathe如果你希望缩放效果来回 yoyo,请使用 breathe 方法。它是一种缩放效果,使精灵看起来好像在呼吸。参数:名称默认值描述anySprite 需要产生效果的精灵endScaleX0.5x 轴缩放的比例endScaleY0.5y 轴缩放的比例durationInFrames60持续时间,以帧为单位yoyotrue是否轮流反向播放delayBeforeRepeat0一个以毫秒为单位的数字,用于确定精灵 yoyo 之前的延迟时间。示例:c.breathe( sprite, //精灵 0.1, //x轴缩放的比例 0.1, //y轴缩放的比例 100, //持续时间,以帧为单位 true, //轮流反向播放 0, //yoyo 之间的延迟时间);查看示例strobe使用 strobe 方法通过快速改变精灵比例,使精灵看起来像闪光灯一样闪烁。参数:只需要传入一个精灵作为参数即可。示例:c.strobe(sprite);查看示例wobble使用 wobble 方法可以使精灵像果冻一样摆动。参数:只需要传入一个精灵作为参数即可。示例:c.wobble(sprite);查看示例如果你使用这些缩放补间效果(scale,breathe,strobe,或者 wobble),将精灵的锚点居中,就可以从精灵的中心进行缩放。sprite.anchor.set(0.5, 0.5);注意: 目前, Charm 这个库支持的 Pixi 版本是 3.0.11。如果使用比较高的版本会有一些问题,比如出现这样的警告。这是因为 Pixi 版本4.0.0起已弃用 ParticleContainer ,改为使用 particles.ParticleContainer 了。所以要解决这个问题需要把 Charm.js 文件中的 ParticleContainer 改为 particles.ParticleContainer 。上一篇 学习 PixiJS — 视觉效果 ...

February 18, 2019 · 3 min · jiezi

学习 PixiJS — 视觉效果

平铺精灵平铺精灵是一种特殊的精灵,可以在一定的范围内重复一个纹理。你可以使用它们创建无限滚动的背景效果。要创建平铺精灵,需要使用带有三个参数的 TilingSprite 类(PIXI.extras.TilingSprite)用法:let tilingSprite = new PIXI.extras.TilingSprite(texture, width, height);参数:名称默认值描述texture 平铺精灵的纹理width100平铺精灵的宽度height100平铺精灵的高度除此之外,平铺精灵具有与普通精灵所有相同的属性,并且与普通精灵的工作方式相同。他们还有 fromImage 和 fromFrame 方法,就像普通精灵一样。以下是如何使用名称是 brick.jpg 的100 x 100像素的图像创建200 x 200像素的平铺精灵。并且从画布左上角偏移30像素。以下是关键代码:let tilingSprite = new PIXI.extras.TilingSprite( PIXI.loader.resources[imgURL].texture, 200, 200);tilingSprite.x = 30;tilingSprite.y = 30;下图显示了 brick.jpg 图像以及上面代码的效果。你可以使用 tilePosition.x 和 tilePosition.y 属性来移动平铺精灵使用的纹理。以下是如何将平铺精灵使用的纹理移动30像素。tilingSprite.tilePosition.x = 30;tilingSprite.tilePosition.y = 30;这里不是在移动平铺精灵,而是移动平铺精灵使用的纹理。下图是两种情况的对比。你还可以使用 tileScale.x 和 tileScale.y 属性更改平铺精灵使用的纹理的比例。以下是如何将平铺精灵使用的纹理的大小增加到1.5倍的关键代码:tilingSprite.tileScale.x = 1.5;tilingSprite.tileScale.y = 1.5;原图 与 上面代码实现的效果的对比:tileScale 和 tilePosition 都有一个 set 方法,可以一行代码设置 x 属性和 y 属性。参数:名称默认值描述x0新的 x 属性值y0新的 y 属性值用法:tilingSprite.tilePosition.set(30, 30);tilingSprite.tileScale.set(1.5, 1.5);平铺精灵是创建重复图像模式的便捷方式。因为你可以移动纹理的位置,所以你可以使用平铺精灵创建无缝的滚动背景。这对于许多类型的游戏都非常有用。让我们来看看如何做到这一点。首先,从无缝平铺图像开始。无缝图像是图案在各方面匹配的图像。如果并排放置图像的副本,它们看起来就像是一个连续的大图像,上面示例中用到的 brick.jpg 就是这种图像。接下来,使用此图像创建一个平铺精灵。然后在游戏循环中更新精灵的 tilePosition.x 属性。关键代码:function play() { tilingSprite.tilePosition.x -= 1;}效果图:查看示例你还可以使用此功能创建一个称为视差滚动的伪3D效果。就是在同一位置层叠多个这样的平铺精灵,并使看上去更远的图像移动得比更近的图像慢。就像下面这个示例一样!两张用于做平铺精灵的图像:实现的效果图:查看示例着色精灵有一个 tint 属性,给这个属性赋值一个十六进制颜色值可以改变精灵的色调。我们来试试吧!关键代码:sprite.tint = 0xFFFF660;原图 与 上面代码实现的效果的对比:查看示例每个精灵的 tint 属性默认值是白色(0xFFFFFF),也就是没有色调。如果你想改变一个精灵的色调而不完全改变它的纹理,就使用着色。蒙版Pixi 允许你使用 Graphics (图形)对象来屏蔽任何精灵或具有嵌套子精灵的容器。蒙版是隐藏在形状区域之外的精灵的任何部分的形状。要使用蒙版,先创建精灵和 Graphics 对象。然后将精灵的 mask 属性设置为创建的 Graphics 对象。示例:首先,用皮卡丘的图像创建一个精灵。然后创建一个蓝色正方形并定位在精灵的上方(形状的颜色并不重要)。最后,精灵的 mask 属性设置为创建的正方形对象。这样会只显示正方形区域内精灵的图像。精灵在正方形之外的任何部分都是不可见的。原图 与 使用蒙版后的对比:关键代码://创建精灵let Pikachu = new PIXI.Sprite(PIXI.loader.resources[imgURL].texture);//创建一个正方形对象let rectangle = new PIXI.Graphics();rectangle.beginFill(0x66CCFF);rectangle.drawRect(0, 0, 200, 200);rectangle.endFill();rectangle.x = 100;rectangle.y = 100;//给精灵设置蒙版Pikachu.mask = rectangle;查看示例你还可以为蒙版设置动画,去做出一些有趣的效果。而且如果是用 WebGL 渲染的话,还可以用精灵作为蒙版。下面这个示例是用三张图片做成精灵,然后把一个精灵作为蒙版,并且给蒙版设置动画的示例。效果图:查看示例混合模式blendMode 属性确定精灵如何与其下层的图像混合。如下所示,可以将它们应用于精灵:sprite.blendMode = PIXI.BLEND_MODES.MULTIPLY;以下是可以使用的17种混合模式的完整列表:没有混合NORMAL(正常)对比比较(饱和度模式)SOFT_LIGHT(柔光)HARD_LIGHT(强光)OVERLAY(叠加)对比比较(差集模式)DIFFERENCE(差值)EXCLUSION(排除)减淡效果(变亮模式)LIGHTEN(变亮)COLOR_DODGE(颜色减淡)SCREEN(滤色)ADD(线性减淡,添加)加深效果(变暗模式)DARKEN(变暗)COLOR_BURN(颜色加深)MULTIPLY(正片叠底)色彩效果(颜色模式)HUE(色相)SATURATION(饱和度)COLOR(颜色)LUMINOSITY(明度)这些混合模式和图像编辑器,比如 Photoshop 中使用的混合模式是一样的,如果你想尝试每种混合模式,你可以在 Photoshop 中打开一些图像,将这些混合模式应用于这些图像上,观察效果。注意: WebGL 渲染器仅支持 NORMAL,ADD,MULTIPLY 和 SCREEN 混合模式。任何其他模式都会像 NORMAL 一样。查看示例滤镜Pixi 拥有多种滤镜,可以将一些特殊效果应用于精灵。所有滤镜都在 PIXI.filters 对象中。滤镜是 Pixi 最好的功能之一,因为它们可以让你轻松创建一些特殊效果,否则只有通过复杂的低级 WebGL 编程才能实现这些效果。这是一个如何创建 BlurFilter (模糊滤镜)的示例(其他滤镜遵循相同的格式)://创建一个模糊滤镜let blurFilter = new PIXI.filters.BlurFilter();//设置模糊滤镜的属性blurFilter.blur = 20;//将模糊滤镜添加到精灵的滤镜数组中sprite.filters = [blurFilter];Pixi 的所有显示对象(Sprite 和 Container 对象)都有一个滤镜数组。要向精灵添加滤镜,先创建滤镜,然后将其添加到精灵的滤镜数组中。你可以根据需要添加任意数量的滤镜。sprite.filters = [blurFilter, sepiaFilter, displacementFilter];使用它就像使用其他普通数组一样。要清除所有精灵的滤镜,只需清除数组即可。sprite.filters = [];除了这些属性,所有滤镜还包括额外的 padding 和 uniforms 属性。padding 增加了滤镜区域周围的空间。uniforms 是一个可用于向 WebGL 渲染器发送额外值的对象。在日常使用中,你永远不必担心设置 uniforms 属性。PixiJS 在4.0.0版本的时候,将非核心滤镜转移到新的包 — pixi-filters,现在 PixiJS 内置的滤镜有下面这几种。AlphaFilter用来修改对象透明度的滤镜。 在其他一些文档中,你可能看到的是 VoidFilter 这个滤镜,这是因为在 PixiJS 的4.6.0版本的时候,才添加 AlphaFilter,而弃用 VoidFilter。BlurFilterBlurFilter 将高斯模糊应用于对象。可以分别为x轴和y轴设置模糊强度。BlurXFilterBlurXFilter 将水平高斯模糊应用于对象。BlurYFilterBlurYFilter 将垂直高斯模糊应用于对象。ColorMatrixFilterColorMatrixFilter 类允许你对 显示对象(displayObject) 上每个像素的 RGBA 颜色和 alpha 值应用5x4矩阵变换,以生成一组具有新的 RGBA 颜色和 alpha 值的结果。它非常强大!使用它可是实现黑白、调整亮度、调整对比度、去色、灰度、调整色调,等许多效果。DisplacementFilterDisplacementFilter 类使用指定纹理(称为置换贴图)中的像素值来执行对象的位移。你可以使用这个滤镜来实现扭曲的效果。在这篇文章中已经讲过什么是 DisplacementFilter(置换滤镜)了,并且文章中也有一个不错的示例。FXAAFilter快速近似抗锯齿滤镜。NoiseFilter杂色效果滤镜。注意:Pixi 的滤镜仅适用于 WebGL 渲染,因为 Canvas 绘图 API 太慢而无法实时更新它们。这里有一个示例,包含了 Pixi 中绝大部分的滤镜。查看示例视频纹理你可以将视频用作精灵的纹理,就像使用图像一样容易。使用 Texture 类的 fromVideo 方法就可以创建视频纹理。videoTexture = PIXI.Texture.fromVideo(videoUrl);videoSprite = new PIXI.Sprite(videoTexture);stage.addChild(videoSprite);或者,也可以使用 fromVideoUrl 方法从 URL 地址创建视频纹理。视频纹理只是一个普通的 HTML5 <video> 元素,你可以通过纹理的 baseTexture.source 属性访问它,如下所示:let videoSource = videoTexture.baseTexture.source;然后,你可以使用任何 HTML5 <video> 元素的属性和方法控制视频,例如 play 和 pause 。videoSource.play();videoSource.pause();查看 HTML <video> 元素的完整规范,可以知道所有可以使用的属性和方法。查看示例适配多种分辨率如果你对物理像素、设备独立像素、设备像素比,等一些名词还不熟悉,可以先看看这篇文章 。Pixi 会自动调整像素密度,以匹配运行内容的设备的分辨率。你所要做的就是为高分辨率和低分辨率提供不同的图像,Pixi 将帮助你根据当前的设备像素比选择正确的图像。注意:当你创建高分辨率图像时,可以将“@2x”添加到图像文件名称后面,以说明图像是支持高分辨率的屏幕,例如,Retina 屏幕。同时这也会设置精灵的 baseTexture.resolution 属性(sprite.texture.baseTexture.resolution)。第一步是找出当前的设备像素比。你可以使用 window.devicePixelRatio 方法执行此操作。将此值分配给变量。let displayResolution = window.devicePixelRatio;displayResolution 是一个描述设备像素比的数字。它由运行应用程序的设备自动提供。1是标准分辨率; 2是高密度分辨率; 你将越来越多地发现一些报告3的超高密度显示器。下一步是将此值分配给渲染选项的 resolution 属性。在创建 Pixi 应用时执行此操作,如下所示://创建一个 Pixi应用 需要的一些参数let option = { width: 640, height: 360, transparent: true, resolution: displayResolution}//创建一个 Pixi应用let app = new PIXI.Application(option);然后根据设备像素比选择正确的图像加载到纹理中。如下所示:let texture;if (displayResolution === 2) { //加载高分辨率图像 texture = PIXI.Texture.fromImage(“highResImage@2x.png”);} else { //加载普通分辨率图像 texture = PIXI.Texture.fromImage(“normalResImage.png”);}let anySprite = new PIXI.Sprite(texture);如果你需要知道加载纹理的设备像素比是多少,可以使用 texture 的 baseTexture.resolution 属性(texture.baseTexture.resolution)找出。查看示例绳(Rope)另一个有趣的效果是 Rope。它允许精灵像波浪一样振荡或像蛇一样滑行,如下图所示。首先,从想要变形的事物的图像开始。滑行蛇实际上是一个简单的直线图像,如下图所示。然后决定你想要独立移动蛇的段数。蛇图像的宽度为600像素,因此大约20个片段会产生很好的效果。将图像宽度除以段数,就是每个绳段的长度。let numberOfSegments = 20;let imageWidth = 600;let ropeSegment = imageWidth / numberOfSegments;接下来,创建一个包含20个 Point 对象的数组。每个 Point 的 x 位置(第一个参数)将与下一个 Point 分开一个 ropeSegment 的距离。let points = [];for (let i = 0; i < numberOfSegments; i++) { points.push(new PIXI.Point(i * ropeLength, 0));}现在使用 PIXI.mesh.Rope 方法 new 一个 Rope 对象。这个方法需要两个参数:一个是 Rope 对象使用的纹理一个是包含 Point 对象的数组let snake = new PIXI.mesh.Rope(PIXI.Texture.fromImage(‘snake.png’), points);将蛇添加到一个容器中,这样可以更容易定位。然后将容器添加到舞台并定位它。let snakeContainer = new PIXI.Container();snakeContainer.addChild(snake);stage.addChild(snakeContainer);snakeContainer.position.set(10, 150);现在为游戏循环中的 Point 对象设置动画。通过 for 循环将数组中的每个 Point 按照椭圆形的轨迹移动,形成波浪效果。count += 0.1;for (let i = 0; i < points.length; i++) { points[i].y = Math.sin((i * 0.5) + count) * 30; points[i].x = i * ropeLength + Math.cos((i * 0.3) + count) * numberOfSegments;}查看示例 这里还有一篇文章,讲的是用 Rope 来实现游动的锦鲤的效果,看上去也很好玩。总结本文主要聊了聊平铺精灵、着色、蒙版、混合模式、滤镜、视频纹理、适配多种分辨率、绳(Rope),相关的东西。如果你觉得文字解释的不清楚,在每小节中,都有一个或者多个相应的示例,你可以点开看看,而且示例中的注释也比较清楚。 还有就是因为 PixiJS 的 API 时常有变化,所以要注意 PixiJS 的版本,文中大部分示例用的版本是4.8.2,如果你在尝试使用的时候,发现和示例的效果不一样,可以先检查一下版本。如果文中有错误的地方,还请小伙伴们指出,万分感谢。 ...

February 3, 2019 · 2 min · jiezi

Animate.css 超强CSS3动画库,三行代码搞定H5页面动画特效!

一、基本用法引入CSS依赖<link rel=“stylesheet” href=“https://cdn.bootcss.com/animate.css/3.7.0/animate.min.css">在元素的Class中加以下内容animated (必选)infinite (可选) 无限重复bounce (必选) 动画样式 参考下方表格delay-2s (可选) 延迟出现 <div class=“animated infinite bounce delay-2s”><h1>Example</h1></div>Class Name bounceflashpulserubberBandshakeheadShakeswingtadawobblejellobounceInbounceInDownbounceInLeftbounceInRightbounceInUpbounceOutbounceOutDownbounceOutLeftbounceOutRightbounceOutUpfadeInfadeInDownfadeInDownBigfadeInLeftfadeInLeftBigfadeInRightfadeInRightBigfadeInUpfadeInUpBigfadeOutfadeOutDownfadeOutDownBigfadeOutLeftfadeOutLeftBigfadeOutRightfadeOutRightBigfadeOutUpfadeOutUpBigflipInXflipInYflipOutXflipOutYlightSpeedInlightSpeedOutrotateInrotateInDownLeftrotateInDownRightrotateInUpLeftrotateInUpRightrotateOutrotateOutDownLeftrotateOutDownRightrotateOutUpLeftrotateOutUpRighthingejackInTheBoxrollInrollOutzoomInzoomInDownzoomInLeftzoomInRightzoomInUpzoomOutzoomOutDownzoomOutLeftzoomOutRightzoomOutUpslideInDownslideInLeftslideInRightslideInUpslideOutDownslideOutLeftslideOutRightslideOutUpheartBeat大功告成,刷新页面即可查看动画效果。基本用法就是这些官方还给出了一些进阶用法如下二、进阶用法动态调用动画的Javascript例子function animateCss(element, animationName, callback) { const node = document.querySelector(element) node.classList.add(‘animated’, animationName) function handleAnimationEnd() { node.classList.remove(‘animated’, animationName) node.removeEventListener(‘animationend’, handleAnimationEnd) if (typeof callback === ‘function’) callback() } node.addEventListener(‘animationend’, handleAnimationEnd)}三、在官方例子基础上,稍加修改以后由于官方例子用的是querySelector,故只会选中第一个符合要求的元素。并且持续时间只有slow(2s)、slower(3s)、fast(800ms)、faster(500ms)故我稍加修改,依然用的原生JS语法(部分ES6)其中选择器element改为选中所有符合要求的元素新增times参数,可以是2000ms或者2s/** * element: 选择器 例如 #id | .class | div * animationName: 动画名称 参考animate.css官网 例如fadeIn * times: 持续时间 例如 200ms | 2s * callback: 回调函数 */function animateCss(element, animationName,times, callback) { const nodes = document.querySelectorAll(element) nodes.forEach((node => { if(times) node.style.setProperty(‘animation-duration’, times, ‘important’); node.classList.add(‘animated’, animationName) function handleAnimationEnd() { node.classList.remove(‘animated’, animationName) node.removeEventListener(‘animationend’, handleAnimationEnd) if (typeof callback === ‘function’) callback() } node.addEventListener(‘animationend’, handleAnimationEnd) }))}例子animateCss(’.post’, ‘pulse’);animateCss(’.post’, ‘pulse’,‘200ms’);animateCss(’.post’, ‘pulse’,‘200ms’,function(){//do something});Animate.css官网https://daneden.github.io/animate.css/https://github.com/daneden/animate.css另外本篇文章也发表在了我的个人主页,欢迎来查看https://zzzmh.cn/single?id=59 ...

January 23, 2019 · 1 min · jiezi

学习 PixiJS — 粒子效果

你如何创造火,烟,魔法和爆炸等效果?你制作了许多小精灵,几十,几百,甚至上千个精灵。然后对这些精灵应用一些物理效果,使它们的行为类似于你尝试模拟的元素。你还必须给他们一些关于它们应该如何出现和消失以及应该形成什么样的模式的规则。这些微小的精灵被称为粒子。你可以使用它们为游戏制作各种特效。使用 Dust 库PIXI 没有内置的制作粒子效果的功能,但你可以使用一个名为 Dust 的轻量级的库来制作它们。注意:Dust 是一种快速简便的方法,可以制作游戏所需的大部分粒子效果,但如果你需要功能更全面,更复杂的库,请查看 Proton 使用 Dust 库和使用 SpriteUtilities 库是一样的。首先直接用 script 标签,引入 js 文件<script src=“https://www.kkkk1000.com/js/dust.js"></script>然后创建它的实例d = new Dust(PIXI);变量 d 现在就代表 Dust 实例。接下来,在游戏循环中调用 Dust 的 update 方法,这个方法用于更新粒子。我们在上篇文章中制作的示例中有 gameLoop 和 play 两个函数 ,你可以在这两个函数中执行此操作。建议在 gameLoop 中执行此操作,就在调用 state 函数之后但在渲染阶段之前,如下所示:function gameLoop(){ requestAnimationFrame(gameLoop); state(); d.update(); renderer.render(stage);}制作粒子制作粒子需要用到 Dust 库的 create 方法 参数:名称类型默认值描述xnumber0粒子出现的 x 坐标ynumber0粒子出现的 y 坐标spriteFunctionfunction 一个函数,它返回要用于每个粒子的精灵,如果提供具有多个帧的精灵,Dust 将随机显示不同帧containerobject一个 PIXI 容器要添加粒子的容器numberOfParticlesnumber20要创建的粒子数gravitynumber0重力randomSpacingbooleantrue随机间隔minAnglenumber0最小角度maxAnglenumber6.28最大角度minSizenumber4最小尺寸maxSizenumber16最大尺寸minSpeednumber0.3最小速度maxSpeednumber3最大速度minScaleSpeednumber0.01最小比例速度maxScaleSpeednumber0.05最大比例速度minAlphaSpeednumber0.02最小alpha速度maxAlphaSpeednumber0.02最大alpha速度minRotationSpeednumber0.01最小旋转速度maxRotationSpeednumber0.03最大旋转速度返回值:返回一个数组,其中包含对用作粒子的所有精灵的引用,如果需要进行碰撞检测等原因必须访问它们,这可能很有用。现在我们来试试这个方法。示例代码:<!doctype html><html lang=“zn”><head> <meta charset=“UTF-8”></head><body> <div id=“px-render”></div> <script src=“https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.2/pixi.min.js"></script> <script src=“https://www.kkkk1000.com/js/spriteUtilities.js"></script> <script src=“https://www.kkkk1000.com/js/dust.js"></script> <script> //创建一个 Pixi应用 需要的一些参数 let option = { width: 400, height: 300, transparent: true, } //创建一个 Pixi应用 let app = new PIXI.Application(option); //获取舞台 let stage = app.stage; //获取渲染器 let renderer = app.renderer; let playground = document.getElementById(‘px-render’); //把 Pixi 创建的 canvas 添加到页面上 playground.appendChild(renderer.view); let su = new SpriteUtilities(PIXI); let d = new Dust(PIXI); //需要加载的图片的地址 let imgURL = “https://www.kkkk1000.com/images/learnPixiJS-ParticleEffects/star.png"; //加载图像,加载完成后执行setup函数 PIXI.loader.add(imgURL).load(setup); function setup() { stars = d.create( 128, //x 起始坐标 128, //y 起始坐标 () => su.sprite(imgURL), //返回要用于每个粒子的精灵 stage, //粒子的容器 50, //粒子数 0,//重力 false,//随机间隔 0, 6.28,//最小/最大角度 30, 90,//最小/最大尺寸 1, 3//最小/最大速度 ); //开始游戏循环 gameLoop(); } function gameLoop() { requestAnimationFrame(gameLoop); d.update(); renderer.render(stage); } </script></body></html>查看效果使用 ParticleContainer在前面的示例代码中,我们创建的粒子都被添加到根容器(第四个参数)。但是,你可以将粒子添加到任何你喜欢的容器或任何其他精灵。PIXI 有一个叫 ParticleContainer 的方法,任何在 ParticleContainer 里的精灵都会比在一个普通的 Container 的渲染速度快2到5倍。到这里可以了解 ParticleContainer 如果要对粒子使用 ParticleContainer,只需在 create 方法的第四个参数中添加要使用的 ParticleContainer 对象的名称。以下是修改前面的示例代码以将粒子添加到名为 starContainer 的 ParticleContainer 的方法。//创建ParticleContainer并将其添加到stagelet starContainer = new PIXI.particle.ParticleContainer( 1500, { alpha: true, scale: true, rotation: true, uvs: true });stage.addChild(starContainer);function setup() { //创建星形粒子并将它们添加到starContainer stars = d.create( 128, //x 起始坐标 128, //y 起始坐标 () => su.sprite(imgURL), starContainer, //粒子的容器 50, //粒子数 0,//重力 false,//随机间隔 0, 6.28,//最小/最大角度 30, 90,//最小/最大尺寸 1, 3//最小/最大速度 ); //开始游戏循环 gameLoop();}查看效果 ParticleContainers 针对推送数千个精灵进行了优化,因此,除非你为很多粒子设置动画,否则你可能不会注意到对于使用普通 Container 对象的性能提升。使用粒子发射器create 方法会产生一次粒子爆发,但通常你必须产生连续的粒子流。你可以在粒子发射器的帮助下完成此操作。粒子发射器以固定的间隔产生粒子以产生流效果,你可以使用 Dust 的 emitter 方法创建一个粒子发射器。发射器具有 play 和 stop 方法,可让打开和关闭粒子流,并可以定义粒子的创建间隔。下面的代码是使用 Dust 的 emitter 方法的一般格式。它需要两个参数。第一个参数是创建粒子间隔(以毫秒为单位)。第二个参数与我们在前面的示例中使用的 create 方法相同。let particleStream = d.emitter( 100, () => d.create(););任何100毫秒或更短的间隔值将使颗粒看起来以连续流的形式流动。这里有一些产生星形喷泉效果的代码。let particleStream = d.emitter( 100, () => d.create( 128, 128, () => su.sprite(imgURL), stage, 30, 0.1, false, 3.14, 6.28, 30, 60, 1, 5 ) );第六个参数,0.1,是重力。将重力设置为更高的数字,粒子将更快的下落。角度介于3.14和6.28之间。这使得粒子出现在其原点之上的半月形大小的角度内。下图说明了如何定义该角度。星星在中心原点处创建,然后在圆圈的上半部分向上飞出。然而,星星在重力的作用下,最终将落在画布的底部,这就是产生星形喷泉效果的原因。你可以使用 emitter 的 play 和 stop 方法在代码中随时打开或关闭粒子流,如下所示:particleStream.play();particleStream.stop();效果图:完整代码:<!doctype html><html lang=“zn”><head> <meta charset=“UTF-8”></head><body> <div id=“px-render”></div> <script src=“https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.2/pixi.min.js"></script> <script src=“https://www.kkkk1000.com/js/spriteUtilities.js"></script> <script src=“https://www.kkkk1000.com/js/dust.js"></script> <script> //创建一个 Pixi应用 需要的一些参数 let option = { width: 400, height: 300, transparent: true, } //创建一个 Pixi应用 let app = new PIXI.Application(option); //获取舞台 let stage = app.stage; //获取渲染器 let renderer = app.renderer; let playground = document.getElementById(‘px-render’); //把 Pixi 创建的 canvas 添加到页面上 playground.appendChild(renderer.view); let su = new SpriteUtilities(PIXI); let d = new Dust(PIXI); let particleStream; //需要加载的图片的地址 let imgURL = “https://www.kkkk1000.com/images/learnPixiJS-ParticleEffects/star.png"; //加载图像,加载完成后执行setup函数 PIXI.loader.add(imgURL).load(setup); function setup() { let particleStream = d.emitter( 100, () => d.create( 128, 128, () => su.sprite(imgURL), stage, 30, 0.1, false, 3.14, 6.28, 30, 60, 1, 5 ) ); particleStream.play(); //开始游戏循环 gameLoop(); } function gameLoop() { requestAnimationFrame(gameLoop); d.update(); renderer.render(stage); } </script></body></html>查看效果上一篇 学习 PixiJS — 精灵状态 ...

January 22, 2019 · 2 min · jiezi

学习 PixiJS — 精灵状态

精灵状态如果你有复杂的游戏角色或交互式对象,你可能希望该角色根据游戏环境中发生的情况,以不同的方式运行。每个单独的行为称为状态。如果你在精灵上定义状态,那么只要游戏中出现与该状态相对应的事件,就可以触发这些状态。比如,通过键盘的方向键控制一个游戏角色时,按下左箭头,角色就向左移动,其实可以理解为,按下左键头时,触发了角色的向左移动的状态。如果要开始使用精灵状态,首先需要一个状态播放器。状态播放器用于控制精灵状态。Pixi 精灵没有自己的状态播放器,但你可以使用 SpriteUtilities 库中的 sprite 的方法,该方法将创建一个内置状态播放器的精灵。 SpriteUtilities 库的使用上一篇提到过了,可以看 学习 PixiJS — 动画精灵 这篇文章。sprite定义:使用 sprite 函数制作任何类型的 Pixi 精灵。用法:let anySprite = su.sprite(frameTextures, xPosition, yPosition);参数:第一个参数 frameTextures 可以是以下任何一个:一个 PNG 图像字符串一个Pixi 纹理对象纹理图集帧 id 数组一个 PNG 图像字符串的数组一个 Pixi 纹理对象数组如果你为 sprite 方法提供一个数组,它将返回一个动画精灵,这个动画精灵会内置了一个状态播放器。状态播放器只是四个新属性和方法的集合,用于控制精灵动画状态。fps:用于设置精确的动画速度的属性,以每秒帧数为单位。它的默认值是12,fps 与游戏循环 fps 无关,这意味着你可以让精灵动画以独立于游戏或应用程序速度的速度播放。playAnimation:一种播放精灵动画的方法。如果要播放帧的子集,就传入开始帧编号和结束帧编号两个参数。默认情况下,动画将循环播放,除非你将精灵的 loop 属性值设置为 false 。stopAnimation:一种在当前帧停止精灵动画的方法。show:接受参数是一个数字,用来显示特定帧编号的方法。第二个参数 xPosition 和 第三个参数 yPosition 表示创建的精灵的 x 和 y 坐标。什么是精灵状态?下图是一个游戏角色的 PNG 图像,其中包含使角色看起来像是在四个不同方向行走所需的所有帧。这个雪碧图中实际上有八个精灵状态:四个静态状态和四个动画状态。让我们看看这些状态是什么以及如何定义它们。静态状态精灵的静态状态定义精灵在不移动时的四个位置。这些状态是:down, left, right,和up。下图显示了雪碧图上的状态以及标识这些状态的帧号。可以看到第0帧是向下状态,第4帧是左侧状态,第8帧是右侧状态,第12帧是向上状态。怎么定义这些状态呢?首先,创建精灵,以下代码展示了如何使用 sprite 方法创建精灵。let frames = su.filmstrip(“images/adventuress.png”, 32, 32);let adventuress = su.sprite(frames);接下来,在精灵上创建一个名为 states 的对象字面量属性。并在 states 对象中创建down,left,right,和up 的键。将每个键的值设置为与状态对应的帧编号。adventuress.states = { down: 0, left: 4, right: 8, up: 12};接下来就是使用精灵的 show 方法来显示正确的状态。例如,以下代码展示如何显示精灵的 left 状态:adventuress.show(adventuress.states.left);下图显示了改变这些状态对精灵外观的影响。你在可以在任何你需要的地方使用它,让精灵对游戏世界的变化作出反应。比较常见的一个场景是在键盘按键的时候,这样你就可以通过箭头键的方向改变精灵面向的方向。例如,按下左箭头键时,你可以通过以下方式将精灵转向左侧。//左箭头按下left.press = () => { //显示left状态 adventuress.show(adventuress.states.left);};只需对其余的箭头键使用相同的格式,就可以使精灵面向所有的四个方向。动画状态精灵的动画状态定义了精灵移动时的四个动作序列。这些状态是:walkDown,walkLeft,walkRight,和walkUp 。下图显示了这些状态在雪碧图上的位置。这些状态中的每一个由四个帧组成,当在循环中播放时,将创建连续的步行动画。要定义每个动画状态,就在 states 对象中创建描述该状态的键。键的值应该是一个包含两个元素的数组:起始帧编号和结束帧编号。例如,以下是如何定义 walkLeft 状态://3是动画序列 开始的帧编号,5是结束的帧编号walkLeft: [3, 5]以下是如何将这四种新动画状态添加到 adventuress 精灵中:adventuress.states = { down: 0, left: 4, right: 8, up: 12, walkDown: [0, 3], walkLeft: [4, 7], walkRight: [8, 11], walkUp: [12, 15]};现在它的状态都被定义了,让我们做一个会走的精灵。 把制作动画精灵和定义状态还有键盘响应所学到的知识相结合,就可以制作一个步行游戏角色。查看效果如果希望精灵在屏幕上移动得更快或更慢,就在箭头键方法中更改 vx 和 vy 的值。如果希望精灵的步行动画效果更快或更慢,就更改精灵的 fps 属性。制作动画帧的工具使用 Adobe Illustrator 或 Photoshop 手动绘制每个帧。Flash Professional 只需将动画导出为雪碧图,就可以在 JavaScript 游戏中使用它。你还可以使用 Shoebox 等工具将 Flash 的 SWF 文件格式转换为纹理图集。Piskel 是一个免费的在线工具,用于制作像素风格的动画游戏角色。Dragon Bones,Spine,和 Creature。这三个工具都非常相似。它们可以创建复杂的游戏角色,为它们设置动画,并将它们导出为雪碧图和 JSON 文件。Shoebox 是一款基于Adobe Air 的免费应用程序,它的功能挺多,比如可以用来制作雪碧图,也可以拆分雪碧图,还可以检测透明图像中的精灵并将其剪切出来 等。上一篇 学习 PixiJS — 动画精灵 ...

January 19, 2019 · 1 min · jiezi

这样做动画交互,一点都不费力!

本文由云+社区发表作者:paulzeng导语:Lottie是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,可实现非常复杂的动画,使用也及其简单,极大释放人力,值得一试。一、简介Lottie 是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,能分析 Adobe After Effects 导出的动画,并且能让原生 App 像使用静态素材一样使用这些动画,完美实现动画效果。现在使用各平台的 native 代码实现一套复杂的动画是一件很困难并且耗时的事,我们需要为不同尺寸的屏幕加载不同的素材资源,还需要写大量难维护的代码,而Lottie可以做到同一个动画文件在不同平台上实现相同的效果,极大减少开发时间,实现不同的动画,只需要设置不同的动画文件即可,极大减少开发和维护成本。官方效果图:二、如何使用Lottie支持多平台,使用同一个JSON动画文件,可在不同平台实现相同的效果。Android 通过Airbnb的开源项目lottie-android实现,最低支持 API 16;IOS 通过Airbnb的开源项目lottie-ios实现,最低支持 IOS 7;React Native,通过Airbnb的开源项目lottie-react-native实现;这是React logo的动画,以下以Android平台为例如何使用Lottie1.下载Lottie在项目的 build.gradle 文件添加依赖dependencies { compile ‘com.airbnb.android:lottie:2.1.0’}2.添加 Adobe After Effects 导出的动画文件Lottie默认读取Assets中的文件,我们需要把动画文件react.json 保存在app/src/main/assets文件里。(文件比较大,只展示了部分内容,文件链接){ “v”: “4.6.0”, “fr”: 29.9700012207031, “ip”: 0, “op”: 141.000005743048, “w”: 800, “h”: 800, “ddd”: 0, “assets”: [ ], “layers”: [ { “ddd”: 0, “ind”: 0, “ty”: 4, “nm”: “center_circle”, “ks”: {…}, “ao”: 0, “shapes”: […], “ip”: 0, “op”: 900.000036657751, “st”: 0, “bm”: 0, “sr”: 1 }, {…}, {…}, {…} ]}3.使用Lottie在布局文件中直接添加Lottie的LottieAnimationView控件,即可在界面显示React logo动画效果<com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width=“wrap_content” android:layout_height=“wrap_content” app:lottie_fileName=“react.json” app:lottie_loop=“true” app:lottie_autoPlay=“true” />4.引入Lottie影响(1)兼容性Lottie 最低支持版本API 16,低版本系统需要做降级动画或者不展示动画(2)安装包影响项使用前使用后结论方法数144807145891增加1084个方法安装包大小41969KB42037KB增大68KB这是用全民K歌release包的测试数据,lottie本身方法数不小,有方法数超标和安装包过大的风险,业务可自行评估注:LottieAnimationView继承于V7的AppCompatImageView,需要引入V7兼容包,根据业务需要,可以源码引入Lottie,让LottieAnimationView继承与ImageView,就不用引入V7兼容包,可减小安装大小。三、使用小技巧1.加载SDCard动画文件StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(JSON_PATH + “react.json”)));String content = null;while ((content = bufferedReader.readLine()) != null){ stringBuilder.append(content);}JSONObject jsonObject = new JSONObject(stringBuilder.toString());animationView.setAnimation(jsonObject);animationView.loop(true);animationView.playAnimation();2.加载SDCard图片animationView.setImageAssetDelegate(new ImageAssetDelegate() { @Override public Bitmap fetchBitmap(LottieImageAsset asset) { try { FileInputStream fileInputStream = new FileInputStream(IMAGE_PATH + asset.getFileName()); return BitmapFactory.decodeStream(fileInputStream); ///把流转化为Bitmap图片 } catch (Exception e) { Log.e(TAG, “”, e); } return null; }});3.加载SDCard字体animationView.setFontAssetDelegate(new FontAssetDelegate(){ public Typeface fetchFont(String fontFamily) { Typeface customFont = Typeface.createFromFile(FONT_PATH + fontFamily); return customFont; }});4.缓存动画/** Lottie内部有两个缓存map(强引用缓存,弱引用缓存),在动画文件加载完成后会根据设置的缓存策略缓存动画,方便下次使用。*/animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Strong); //强缓存animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Weak); //弱缓存四、Lottie实现原理设计师把一张复杂的图片使用多个图层来表示,每个图层展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层或者图层中的元素做平移、旋转、收缩等动画。Lottie的使用的资源是需要先通过bodymovin( bodymovin 插件本身是用于网页上呈现各种AE效果的一个开源库)将 Adobe After Effects (AE)生成的aep动画工程文件转换为通用的json格式描述文件。Lottie则负责解析动画的数据,计算每个动画在某个时间点的状态,准确地绘制到屏幕上。导出的json动画描述文件:{ “v”: “4.6.0”, “fr”: 29.9700012207031, “ip”: 0, “op”: 141.000005743048, “w”: 800, “h”: 800, “ddd”: 0, “assets”: [ ], “layers”: [ {…}, ]}Lottie主要类图:图:lottie_classLottie对外通过控件LottieAnimationView暴露接口,控制动画。LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下:1.json文件解析LottieComposition负责解析json文件,建立数据到java对象的映射关系。(1)解析json外部结构LottieComposition封装整个动画的信息,包括动画大小,动画时长,帧率,用到的图片,字体,图层等等。json外部结构{ “v”: “4.6.0”, //bodymovin的版本 “fr”: 29.9700012207031, //帧率 “ip”: 0, //起始关键帧 “op”: 141.000005743048, //结束关键帧 “w”: 800, //动画宽度 “h”: 800, //动画高度 “ddd”: 0, “assets”: […] //资源信息 “layers”: […] //图层信息}//解析json的源码static LottieComposition fromJsonSync(Resources res, JSONObject json) { Rect bounds = null; float scale = res.getDisplayMetrics().density; int width = json.optInt(“w”, -1); int height = json.optInt(“h”, -1); if (width != -1 && height != -1) { int scaledWidth = (int) (width * scale); int scaledHeight = (int) (height * scale); bounds = new Rect(0, 0, scaledWidth, scaledHeight); } long startFrame = json.optLong(“ip”, 0); long endFrame = json.optLong(“op”, 0); float frameRate = (float) json.optDouble(“fr”, 0); String version = json.optString(“v”); String[] versions = version.split("[.]"); int major = Integer.parseInt(versions[0]); int minor = Integer.parseInt(versions[1]); int patch = Integer.parseInt(versions[2]); LottieComposition composition = new LottieComposition( bounds, startFrame, endFrame, frameRate, scale, major, minor, patch); JSONArray assetsJson = json.optJSONArray(“assets”); parseImages(assetsJson, composition); //解析图片 parsePrecomps(assetsJson, composition); parseFonts(json.optJSONObject(“fonts”), composition); //解析字体 parseChars(json.optJSONArray(“chars”), composition); //解析字符 parseLayers(json, composition); //解析图层 return composition; }(2)解析图片资源LottieImageAsset类封装图片信息"assets": [ //资源信息 { //第一张图片 “id”: “image_0”, //图片id “w”: 58, //图片宽度 “h”: 31, //图片高度 “u”: “images/”, //图片路径 “p”: “img_0.png” //图片名称 }, {…} //第n张图片]static LottieImageAsset newInstance(JSONObject imageJson) { return new LottieImageAsset(imageJson.optInt(“w”), imageJson.optInt(“h”), imageJson.optString(“id”), imageJson.optString(“p”));}(3)解析图层Layer封装图层信息,现在lottie只支持PreComp,Solid,Image,Null,Shape,Text这6中图层。“layers”: [ //图层信息 { //第一层动画 “ddd”: 0, “ind”: 0, //layer id 图层 id “ty”: 4, //图层类型 “nm”: “center_circle”, “ks”: {…}, //动画 “ao”: 0, “shapes”: […], “ip”: 0, //inFrame 该图层起始关键帧 “op”: 90, //outFrame 该图层结束关键帧 “st”: 0, //startFrame 开始 “bm”: 0, “sr”: 1 }, {…} //第n层动画]2.如何动起来Lottie时序图:利用属性动画控制进度,每次进度改变通知到每一层,触发LottieAnimationView重绘。(1)利用属性动画计算进度这里用到了属性动画来产生一个0~1的插值,根据不同的插值来设置当前动画进度。代码如下:public LottieDrawable() { animator.setRepeatCount(0); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (systemAnimationsAreDisabled) { animator.cancel(); setProgress(1f); } else { setProgress((float) animation.getAnimatedValue()); } } });}(2)通过CompositionLayer把进度传递到各个图层@Overridepublic void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { super.setProgress(progress); if (timeRemapping != null) { long duration = lottieDrawable.getComposition().getDuration(); long remappedTime = (long) (timeRemapping.getValue() * 1000); progress = remappedTime / (float) duration; } if (layerModel.getTimeStretch() != 0) { progress /= layerModel.getTimeStretch(); } progress -= layerModel.getStartProgress(); for (int i = layers.size() - 1; i >= 0; i–) { layers.get(i).setProgress(progress); }}(3)通知进度改变 void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { if (progress < getStartDelayProgress()) { progress = 0f; } else if (progress > getEndProgress()) { progress = 1f; } if (progress == this.progress) { return; } this.progress = progress; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onValueChanged(); } }(4)最终回调到LottieAnimationView的invalidateDrawable@Overridepublic void invalidateDrawable(@NonNull Drawable dr) { if (getDrawable() == lottieDrawable) { // We always want to invalidate the root drawable so it redraws the whole drawable. // Eventually it would be great to be able to invalidate just the changed region. super.invalidateDrawable(lottieDrawable); } else { // Otherwise work as regular ImageView super.invalidateDrawable(dr); }}(5)最后触发LottieDrawable重绘@Overridepublic void draw(@NonNull Canvas canvas) { … matrix.reset(); matrix.preScale(scale, scale); compositionLayer.draw(canvas, matrix, alpha); //这里会调用所有layer的绘制方法 if (hasExtraScale) { canvas.restore(); }}五、性能1.官方说明如果没有mask和mattes,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。如果存在mattes,将会创建2~3个bitmap。bitmap在动画加载到window时被创建,被window删除时回收。所以不宜在RecyclerView中使用包涵mattes或者mask的动画,否则会引起bitmap抖动。除了内存抖动,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也会降低动画性能。对于简单的动画,在实际使用时性能不太明显。如果在列表中使用动画,推荐使用缓存LottieAnimationView.setAnimation(String, CacheStrategy) 。2.属性动画和Lottie动画对比以下性能对比是以K歌内单个礼物动画效果 属性动画lottie使用硬件加速lottie未使用硬件加速帧率内容CPULottie动画在未开启硬件加速的情况下,帧率、内存,CPU都比属性动画差,开启硬件加速后,性能差不多。3、未开启硬件加速,Lottie动画大小帧率对比0.12倍1倍主要耗时在draw方法,绘制区域越小,耗时越小六、K歌可用的场景1.特性引导视频全民K歌每个大版本的首次启动都会有视频引导动画,每次都会在清晰度和文件大小平衡,最终导出一个大概有3-4M的引导视频,使用lottie可提高动画清晰度和减小安装包大小2.loading动画3.礼物动画这是全民K歌的礼物面板,内部有大量礼物动画,每个礼物动画都不相同,动画过程中有大量的旋转,透明度,大小的变化,需要用属性动画实现,非常麻烦,代码可维护性也比较差。对比使用lottie后,有几大优势:1、100%实现设计效果2、客户端代码量极少,易维护3、每个动画可动态配置动画样式(加载不同的json)4、所有动画都可动态配置,动画配置文件,素材都可从网上加载4.说唱K歌的说唱功能需要歌词按照特定的动画展示出来,这里就涉及歌词放大,缩小,旋转等等特效。实现时,根据当前时间,在canvas上歌词绘制出来,最终再和声音融合在一起生成一个MV视频,这里就导致动画不能特别复杂,并且有一定的规律。如果使用lottie后,可以把效果导出到json动画文件里,客户端加载动画文件,循环设置进度,读取每帧画面,再和声音融合生成MV。优势:1.动画丰富2.代码量少3.可使用设计导出的字体代码animationView.setProgress(progress); //设置当前进度animationView.buildDrawingCache(); //强制缓存绘制数据Bitmap image = animationView.getDrawingCache(); //获取当前绘制数据七、总结1.劣势(1)性能不够好—某些动画特效,内存和性能不够好;相对于属性动画,在展示大动画时,帧率较低2.优势(1)开发效率高—代码实现简单,更换动画方便,易于调试和维护。(2)数据源多样性—可从assets,sdcard,网络加载动画资源,能做到不发版本,动态更新(3)跨平台—设计稿导出一份动画描述文件,android,ios,react native通用Lottie使用简单,易于上手,非常值得尝试。八、参考资料1.GitHub - airbnb/lottie-android: Render After Effects animations natively on Android and iOS2.Lottie的使用及原理浅析 - 彩笔学长 - CSDN博客3.从json文件到炫酷动画-Lottie实现思路和源码分析 - 简书4.Most Popular - LottieFiles此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 9, 2019 · 4 min · jiezi

css实现边框动画

1、css实现边框动画效果如图:<style> body,div{ margin: 0; padding: 0; box-sizing: border-box; } .box{ width: 300px; height: 200px; padding: 20px; margin: 50px auto; color: #fff; background-color: #f60; } .border-box{ position: relative; width: 100%; height: 100%; padding: 20px; } .border-box::before, .border-box::after{ position: absolute; content: ’ ‘; width: 0; height: 0; border: 1px solid transparent; box-sizing: border-box; } .border-box::before{ top: 0; left: 0; /* 鼠标离开后的回退效果,如果不设置回退效果,则鼠标离开后就直接很生硬的隐藏了 */ transition: border-color 0s ease-in 0.8s,width 0.2s ease-in 0.6s,height 0.2s ease-in 0.4s; } .border-box::after{ bottom: 0; right: 0; transition: border-color 0s ease-in 0.4s,width 0.2s ease-in 0.2s,height 0.2s ease-in; } .border-box:hover:before, .border-box:hover:after{ width: 100%; height: 100%; } .border-box:hover::after{ border-bottom-color: #fff; border-left-color: #fff; transition: border-color 0s ease-out 0.4s,width 0.2s ease-out 0.4s,height 0.2s ease-out 0.6s; } .border-box:hover::before{ border-top-color: #fff; border-right-color: #fff; transition: width 0.2s ease-out,height 0.2s ease-out 0.2s; }</style><body> <div class=“box”> <div class=“border-box”>hover查看效果</div> </div></body> ...

January 8, 2019 · 1 min · jiezi

SVG 动画

SVG 动画主要内容SVG 是什么?SVG 的一些常用用途SVG 的基本结构CSS 结合 SVG 生成动画圆环动画logo 描边动画SVG 的动画元素SVG 是什么?SVG ,可缩放矢量图形(Scalable Vector Graphics),是一种用来描述二维矢量图形的 XML 标记语言。 简单地说,SVG 面向图形,HTML 面向文本。SVG 特点SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强。SVG 是矢量的,可伸缩的,适用多种分辨率。SVG 是开放的标准,本质是纯粹的 XML ,可以被非常多的工具读取和修改。SVG 图像中的文本是可选的,同时也是可搜索的。SVG 兼容性IE9+,主流环境基本都能支持。具体参见SVG 的一些常用用途制作iconfont作为图片文件img src属性引用,css background-image引用。SVG作为图像引用时,大多数浏览器不会加载SVG自身引用的文件(其他图像,外部脚本,字体文件等),依据浏览器的安全策略,SVG中定义的脚本也可能不会执行。作为应用程序SVG文件也可以作为<object>元素的data属性引入HTML中。注意,MIME type必须是image/svg+xml。<object data=“image.svg” type=“image/svg+xml”> …</object>混合文档可以直接将<svg>嵌入到XHTML或者HTML5文档中。嵌入到XHTML需要为<svg>指定命名空间(xmlns),嵌入到HTML5则可以省略,解析器会自动识别<svg>。在 HTML 内容中应用 SVG 效果SVG 的基本结构SVG 跟标签<svg xmlns=“http://www.w3.org/2000/svg" version=“1.1” width=“1024px” height=“1024px”></svg>SVG 基本形状1. 矩形(rect)<rect x=“60” y=“10” rx=“20” ry=“40” width=“100” height=“100”/>2. 圆形(circle)<circle cx=“75” cy=“75” r=“60”/>3. 椭圆(ellipse)<ellipse cx=“75” cy=“75” rx=“30” ry=“20”/>4. 线条(line)<line x1=“10” x2=“50” y1=“110” y2=“150” stroke=“black” stroke-width=“3”/>5. 折线(polyline)<polyline points=“60 60, 70 120, 80 130, 70 70” style=“fill: none;stroke-width: 2;stroke: black;”/>6. 多边形(polygon)polygon和折线很像,它们都是由连接一组点集的直线构成。不同的是,polygon的路径在最后一个点处自动回到第一个点。<polygon points=“50 160, 55 180, 70 180, 60 190, 65 205, 50 195, 35 205, 40 190, 30 180, 45 180” style=“fill: none;stroke: black;”/>7. 路径(point)<path d=“M 20 230 Q 40 205, 50 230 T 90230” style=“fill: none;stroke: black”/>路径绘制命令SVG常用元素还有<g>组合,常用属性fill填充,stroke线条颜色,stroke-width线条粗细等等,具体参考MDN文档。 SVG 元素参考SVG 属性参考CSS 结合 SVG 属性生成动画stroke-dasharray可控制用来描边的点划线的图案范式。stroke-dashoffset 属性指定了dash模式到路径开始的距离圆环动画画圆环的动画效果,可用于提示加载百分比,倒计时等场景。https://codepen.io/wishingtre…logo 描边动画webank描边动画SVG 的动画元素兼容性:ie死都不支持,移动端友好。具体参见1.setset元素不能产生动画效果,可以实现基本的延迟功能。就是指:可以在特定时间之后修改某个属性值(包括本身的XML属性和CSS属性值)。2.animateattributeName 变动的属性的属性名。from 变动的初始值。to 变动的终值。dur 动画的持续时间。举个栗子形状补间动画3.animateTransformattributeName固定为transform,可针对transform的相关属性生成动画,不详细介绍了。4.animateMotion(线性路径动画)animateMotion 元素可以让SVG各种图形沿着特定的path路径运动。地球围绕太阳旋转贴合路径弧度运动SVG动画库:1.SVG-Morpheus使用参考演示参考2.snap.svg ...

October 12, 2018 · 1 min · jiezi

大话 JavaScript 动画

背景138.2亿年前,世界上没有时间和空间,或许世界都不存在,在一个似有似无的点上,汇集了所有的物质,它孕育着无限的能量与可能性。宇宙大爆炸巨大的内力已无法被抑制,瞬间爆发,它爆炸了!世界上有了时间和空间,随着岁月的变迁,时光的流逝,无数的星系、恒星、卫星、彗星形成。我们生活的地球,只是茫茫宇宙中的一个小小的天体,或许在遥远的宇宙的另一边,会有平行世界的存在,或许在那里,我们可能是医生、老师、公务员。科学家说我们的宇宙正在加速度的膨胀,暗能量在无限吞噬着暗物质,未来的世界将会变得虚无缥缈。人类起源宇宙的形成,带来了无限可能性,人类释放着欲望和克制,对宇宙的渴望产生于公元前五世纪,古巴比伦人通过观察天体的位置以及外观变化,来预测人世间的各种事物。在遥远的古罗马,人们也舞弄着灵魂,把不羁的想象赋予肉体。Anim,来源于拉丁语,代表着灵魂与生命,代表着所有与生俱来。似乎世间万物都存在联系,宇宙、自然都存在灵魂。动画的形成两万五前年钱的石器时代,石洞中的野兽奔跑分析图,这是人类开始试图捕捉动作最早证据。文艺复兴时期的达芬奇画作上,用两只手臂两条腿来标识上下摆动的动作,在一张画作上做出不同时间的两个动作。直到1906年,世界上第一部动画片《滑稽脸的幽默相》问世。所以动画是否就是将多个画面连起来播放呢?时间是连续的吗?是可以无线分割的吗?我也不太清楚,你看到的流星、人们的动作是连续的吗?或许是吧,毕竟现实生活中还没有像瞬间移动这种事情发生吧。神经可能不是连续的,生物课学过,神经的传递是一个电信号传递过程,并且是颗粒的(神经信号),那么我们看到的东西在我们脑海里的成像一定不是连续的。那么我们为什么能看到连续的动作呢?视觉暂留(Persistence of vision),让我们看到了连续的画面,视神经反应速度大约为1/16秒,每个人不太一样,有些人高一点,一些人低一点。上一次视神经传递的图像将会在大脑中存留,直到下一次神经信号到达。维基百科上说,日常用的日光灯每秒钟大约会熄灭100次,但是你并没有感觉。一般电影的在帧率在24FPS以上,一般30FPS以上大脑会认为是连贯的,我们玩的游戏一般在30FPS,高帧率是60FPS。小时候一定看过翻页动画吧,可以看一看翻页动画-地球进化史前端动画实现Atwood 定律:Any application that can be written in JavaScript will eventually be written in JavaScript.前端做动画不是什么新鲜事了,从jQuery时代,到当下,无不是前端动画横行的时代。我们知道多张不同的图像连在一起就变成了动态的图像。在前端的世界里,浏览器在视觉暂留时间内,连续不断的逐帧输出图像。每一帧输出一张图像。提及动画一定会讨论到帧率(FPS, Frame Per Second),代表每秒输出帧数,也就是浏览器每秒展示出多少张静态的图像。DOM动画中的 CSS3CSS3 动画是当今盛行的 Web 端制作动画的方式之一,对于移动设备来说覆盖率已经非常广泛,在日常开发中可以使用。CSS3 动画只能通过对 CSS 样式的改变控制 DOM 进行动画CSS3 Animation MDNCSS3 Translate MDNDOM动画中的 WebAnimationWebAnimation 还在草案阶段,在Chrome可以尝试使用一下。移动设备还是相当惨烈,iOS 并没有开始支持。Element Animation MDNCSS3 和 WebAnimation 都只能作用于DOM,那么,如果我们想让 Canvas 上的对象产生动画,那我们该怎么办呢?JavaScript既然我们知道动画的原理,其实就是让用户看到连续的图片,并且每一张图片是有变化的。对于事物来讲,我们可以通过改变某些数值来修改他的属性,从来改变他的外在展示。比如正方形的边长,颜色的RGB值,台风的位置(世界坐标),在每一帧去改变这些数值,根据这些数值将对象绘依次制到屏幕上,将会产生动画。通过上面的描述,我们知道,实现一个动画,其实是数值随时间变化,以帧为时间单位。在很久很久以前,JavaScript 使用 setInterval 进行定时调用函数。所以可以使用setInterval来进行数值的改变。为了更好的让各位前端小哥哥小姐姐们做动画,出现了requestAnimationFrame,requestAnimationFrame 接收一个函数,这个函数将在下一帧渲染之前执行,也就是说,不需要太多次的计算,只要在下一帧渲染之前,我们将需要修改的数值修改掉即可。requestAnimationFrame 的帧率和硬件以及浏览器有关,一般是60FPS(16.66666666ms/帧)。我们利用 Dom 进行动画的演示~元素移动创建一个方块<div class=“box”></div>设置宽高和背景颜色.box { width: 100px; height: 100px; background: red;}const box = document.querySelector(’.box’) // 获取方块元素let value = 0 // 设置初始值// 创建每一帧渲染之前要执行的方法const add = () => { requestAnimationFrame(add) // 下一帧渲染之前继续执行 add 方法 value += 5 // 每帧加数值增加5 box.style.transform = translateX(${value}px) // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移}requestAnimationFrame(add) // 下一帧渲染之前执行 add 方法这样,方块每帧向右移动 5 像素,每秒移动60*5=300像素,不是每秒跳动一下,而是一秒在300像素内均匀移动哦。补间动画上一个demo实现了小方块从左到右的移动,但是貌似他会永无止境的移动下去,直到数值溢出,小时候学过flash的朋友都知道补间动画,其实就是让小方块0px到300px平滑移动。其实就是固定的时间点,有固定的位置。所以我们只需要根据运动的已过时间的百分比去计算数值。保持之前的 HTML 和 CSS 不变/** * 执行补间动画方法 * * @param {Number} start 开始数值 * @param {Number} end 结束数值 * @param {Number} time 补间时间 * @param {Function} callback 每帧的回调函数 /function animate(start, end, time, callback) { let startTime = performance.now() // 设置开始的时间戳 let differ = end - start // 拿到数值差值 // 创建每帧之前要执行的函数 function loop() { raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数 const passTime = performance.now() - startTime // 获取当前时间和开始时间差 let per = passTime / time // 计算当前已过百分比 if (per >= 1) { // 判读如果已经执行 per = 1 // 设置为最后的状态 cancelAnimationFrame(raf) // 停掉动画 } const pass = differ * per // 通过已过时间百分比开始结束数值差得出当前的数值 callback(pass) // 调用回调函数,把数值传递进去 } let raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数}我们调用一下补间动画,让数值经过1秒匀速从0变成400。let box = document.querySelector()animate(0, 400, 1000, value => { box.style.transform = translateX(${value}px) // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移})一个简单的匀速补间动画就这么被我们做好了。非匀速动画那万一,这个动画不是非匀速的,比如抖一抖啊,弹一弹,那该怎么办呢?当然也是一样,根据已过时间的百分比去计算数值时间是匀速的,但是数值不是,如果数值变化是有规律的,那么我们就可以使用时间来表示数值,创建一个接收时间比例(当前时间百分比),返回当前位置比例(当前位置百分比)的方法。我们称这个方法叫做缓动方法。如果速度从慢到快,我们可以把时间和数值的图像模拟成以下的样子。公式为 rate = time ^ 2对应的函数应该是function easeIn(time) { // 接收一个当前的时间占总时间的百分比比 return time ** 2}这个实现加速后抖动结束的效果,在Time小于0.6时是一个公式,time大于0.6是另外一个公式。Time < 0.6 时: Rate = Time / 0.6 ^ 2Time > 0.6 时: Rate = Math.sin((Time-0.6) ((3 Math.PI) / 0.4)) * 0.2 + 1最终实现的函数是function shake(time) { if (time < 0.6) { return (time / 0.6) ** 2 } else { return Math.sin((time-0.6) * ((3 * Math.PI) / 0.4)) * 0.2 + 1 }}我们改造一下之前的 animate 函数,接收一个 easing 方法。/** * 执行补间动画方法 * * @param {Number} start 开始数值 * @param {Number} end 结束数值 * @param {Number} time 补间时间 * @param {Function} callback 每帧回调 * @param {Function} easing 缓动方法,默认匀速 /function animate(start, end, time, callback, easing = t => t) { let startTime = performance.now() // 设置开始的时间戳 let differ = end - start // 拿到数值差值 // 创建每帧之前要执行的函数 function loop() { raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数 const passTime = performance.now() - startTime // 获取当前时间和开始时间差 let per = passTime / time // 计算当前已过百分比 if (per >= 1) { // 判读如果已经执行 per = 1 // 设置为最后的状态 cancelAnimationFrame(raf) // 停掉动画 } const pass = differ * easing(per) // 通过已过时间百分比开始结束数值差得出当前的数值 callback(pass) } let raf = requestAnimationFrame(loop) // 下一阵调用每帧之前要执行的函数}测试一下,将我们刚刚创建的 easing 方法传进来加速运动let box = document.querySelector(’.box’)animate(0, 500, 400, value => { box.style.transform = translateX(${value}px) // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移}, easeIn)加速后抖一抖let box = document.querySelector(’.box’)animate(0, 500, 400, value => { box.style.transform = translateX(${value}px) // 将数值设置给 方块 的 css 属性 transform 属性可以控制元素在水平方向上的位移}, shake)总结这些只是 JavaScript 动画基础中的基础,理解动画的原理后,再做动画就更得心应手了。市面上有很多JS动画库,大家可以开箱即用。有一些是针对DOM操作的,也有一些是针对 JavaScript 对象。实现原理你都已经懂了。上述代码已发布到Github: https://github.com/fanmingfei/animation-base我的另外两篇关于动画的文章:为何 Canvas 内元素动画总是在颤抖?前端动画/游戏开发 requestAnimationFrame 之 锁帧 ...

October 5, 2018 · 3 min · jiezi

前端每日实战:139# 视频演示如何用 CSS 和 D3 创作光斑粒子交相辉映的动画

效果预览按下右侧的“点击预览”按钮可以在当前页面预览,点击链接可以全屏预览。https://codepen.io/comehope/pen/zJybdq可交互视频此视频是可以交互的,你可以随时暂停视频,编辑视频中的代码。请用 chrome, safari, edge 打开观看。https://scrimba.com/p/pEgDAM/cGV7phy源代码下载每日前端实战系列的全部源代码请从 github 下载:https://github.com/comehope/front-end-daily-challenges代码解读定义 dom,容器中包含 3 个子元素:<div class=‘container’> <span></span> <span></span> <span></span></div>设置页面背景:body { margin: 0; width: 100vw; height: 100vh; background: radial-gradient(circle at center, #222, black 20%);}定义容器尺寸:.container { width: 100%; height: 100%;}设置光斑的样式,其中定义了偏亮和偏暗的 2 个颜色变量:.container { position: relative;}.container span { –bright-color: #d4ff00; –dark-color: #e1ff4d; position: absolute; width: 30px; height: 30px; margin-left: -15px; margin-top: -15px; background: radial-gradient(var(–bright-color), var(–dark-color)); border-radius: 50%; box-shadow: 0 0 25px 3px var(–dark-color);}把光斑定位到页面中心:.container span { transform: translateX(50vw) translateY(50vh);}增加光斑从中心向四周扩散和收缩的动画效果:.container span { animation: animate 1.5s infinite alternate; animation-delay: calc(var(–n) * 0.015s);}@keyframes animate { 80% { filter: opacity(1); } 100% { transform: translateX(calc(var(–x) * 1vw)) translateY(calc(var(–y) * 1vh)); filter: opacity(0); }}定义动画中用到的变量 –x、–y 和 –n:.container span:nth-child(1) { –x: 20; –y: 30; –n: 1; }.container span:nth-child(2) { –x: 60; –y: 80; –n: 2;}.container span:nth-child(3) { –x: 10; –y: 90; –n: 3;}设置容器的景深,使光斑的运动有从远到近的感觉:.container { perspective: 500px;}.container span { transform: translateX(50vw) translateY(50vh) translateZ(-1000px);}至此,少量元素的动画效果完成,接下来用 d3 批量创建 dom 元素和 css 变量。引入 d3 库,同时删除 html 文件中的子元素和 css 文件中的子元素变量:<script src=“https://d3js.org/d3.v5.min.js"></script>定义光斑粒子数量:const COUNT = 3;批量创建 dom 元素:d3.select(’.container’) .selectAll(‘span’) .data(d3.range(COUNT)) .enter() .append(‘span’);为 dom 元素设置 –x、–y 和 –n 的值,其中 –x 和 –y 是 1 到 99 的随机数:d3.select(’.container’) /* 略 / .style(’–x’, () => d3.randomUniform(1, 99)()) .style(’–y’, () => d3.randomUniform(1, 99)()) .style(’–n’, d => d);再为 dom 元素设置 –bright-color 和 –dark-color 的值:d3.select(’.container’) / 略 */ .style(’–dark-color’, (d) => d3.color(hsl(${70 + d * 0.1}, 100%, 50%))) .style(’–bright-color’, (d) => d3.color(hsl(${70 + d * 0.1}, 100%, 50%)).brighter(0.15));最后,把光斑粒子数量设置为 200 个:const COUNT = 200;大功告成! ...

September 20, 2018 · 2 min · jiezi

用canvas画心电图

效果图:思路:1.模拟点(如果你有真实的数据,那就是把数据幻化成canvas对应的坐标点) 模拟点时注意的点就是高起部分需要对称以及为了好看要随机出现上上下下2.画线 画线需要注意有一个匀速移动的过程。 比如 A点到B点,不是简单的A画到B,而是A点到A1,A2….最后到B(这一块按照比例移动比较难)3.画线的一些效果,比如加上阴影(这里就可以自由发挥了)具体代码<!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <title>心电图</title> <meta name=“viewport” content=“width=device-width, initial-scale=1, user-scalable=no”> <style> html,body{ width: 100%; height: 100%; margin: 0; } canvas{ background: #000; width: 100%; height: 100%; } </style> </head> <body> <div id=“canvas”> <canvas id=“can”></canvas> </div> <script> var can = document.getElementById(‘can’), pan, index = 0, flag = true, wid = document.body.clientWidth, hei = document.body.clientHeight, x = 0, y = hei/2, drawX = 0, drawY = hei/2, drawXY = [], cDrawX = 0, i = 0, reX = 0, reY = 0; start(); function start(){ can.height = hei; can.width = wid; pan = can.getContext(“2d”); pan.strokeStyle = “white”; pan.lineJoin = “round”; pan.lineWidth = 6; pan.shadowColor = “#228DFF”; pan.shadowOffsetX = 0; pan.shadowOffsetY = 0; pan.shadowBlur = 20; pan.beginPath(); pan.moveTo(x,y); drawXYS(); index = setInterval(move,1); }; function drawXYS(){ if(drawX > wid){ }else{ if(drawY == hei/2){ if(flag){ flag = false; }else{ var _y = Math.ceil(Math.random()*10); _y = _y/2; if(Number.isInteger(_y)){ drawY += Math.random()*180+30; }else{ drawY -= Math.random()*180+30; } flag = true; } cDrawX = Math.random()*40+15; }else{ drawY = hei/2; } drawX += cDrawX; drawXY.push({ x : drawX, y : drawY }); drawXYS(); } } function move(){ var x = drawXY[i].x, y = drawXY[i].y; if(reX >= x - 1){ reX = x; reY = y; i++; cc(); return; } if(y > hei/2){ if(reY >= y){ reX = x; reY = y; i++; cc(); return; } }else if(y < hei/2){ if(reY <= y){ reX = x; reY = y; i++; cc(); return; } }else{ reX = x; reY = y; i++; cc(); return; } reX += 1; if(y == hei/2){ reY = hei/2; }else{ var c = Math.abs((drawXY[i].x-drawXY[i-1].x)/(drawXY[i].y-drawXY[i-1].y)); var _yt = (reX-drawXY[i-1].x)/c; if(drawXY[i].y < drawXY[i-1].y){ reY = drawXY[i-1].y - _yt; }else{ reY = drawXY[i-1].y + _yt; } } cc(); } function cc(){ if(i == drawXY.length){ pan.closePath(); clearInterval(index); index = 0; x = 0; y = hei/2; flag = true; i = 0; }else{ pan.lineTo(reX, reY); pan.stroke(); } } </script></body></html>备注代码没有注释,如果有看不懂的地方,可以联系我sf上联系不到的话可以联系我的公众号:乐趣区 ...

September 7, 2018 · 2 min · jiezi