- 小程序蓝牙打印爬坑之旅
因为公司要在小程序上加蓝牙打印标签功能,所以就开始接触小程序的蓝牙打印,看文档还是蛮详细的,而且还有demo,顺着demo,一步一步下来还是蛮顺畅的,原以为很快就能完成。没想到坑来了,由于demo中writeBLECharacteristicValue只是写入了一个16进制的数据,而现实中是需要发送字符串的,而且小程序必须要是arrayBuffer,就必须将字符串转arrayBuffer了,好,网上搜下,准备打印了吱吱吱咦,怎么有乱码啊,怎么中文都乱码了。。这下可糟了!于是就去各种找答案。最后知道问题了:原来是因为我们公司用的打印机是智能支持GB2312编码格式的二进制的,但是字符串是utf-8,诶,又得爬坑。经过一天的努力,终于找到解决方法啦,感谢csdn的大大们。实现的代码如下 //计算arraybuffer的长度 sumStrLength(str) { var length = 0; var data = str.toString(); for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 length += 2; } else { length += 1; } } return length; }, //混杂 hexStringToBuff(str) { //str=‘中国:WXHSH’ const buffer = new ArrayBuffer((this.sumStrLength(str)) + 1); const dataView = new DataView(buffer) var data = str.toString(); var p = 0; //ArrayBuffer 偏移量 for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 //调用GBK 转码 var t = gbk.$URL.encode(data[i]); for (var j = 0; j < 2; j++) { var temp = parseInt(t[j * 2] + t[j * 2 + 1], 16) dataView.setUint8(p++, temp) } } else { var temp = parseInt(data.charCodeAt(i).toString(16), 16) dataView.setUint8(p++, temp) } } console.log(String.fromCharCode.apply(null, new Uint8Array(buffer))); return buffer; }, //js正则验证中文 isCN(str) { if (/[1]+$/.test(str)) { return true; } else { return false; } }, 将中文转化为GB2312编码格式再转成arrayBuffer就大功告成啦,把这个文章记录下来,希望可以帮助到其他小程程们。如有需要,加我Q:786914253 \u3220-\uFA29 ↩︎
2019-03-19 - 通过小程序中的引导关注公众号之后怎么获取openid?
因为现在小程序通知改成了订阅消息,对于消费者下单,商家无法直接通过小程序进行提醒,所以我们打算引导到服务号上,然后通过服务号发送模板消息。 如何在关注公众号后,不进行网页授权的形式下直接获取openid,为的是减少一步操作,因为有些商户年纪比较大,多一步操作会大大提高门槛。 我发现其它服务号是能够实现关注后直接接收到通知的,所以问下 怎么实现的 。
2020-01-09 - request:fail 合集(各种 request:fail 问题)
小程序最近经常碰到 request:fail timeout / request:fail 请求超时 然后我写了一个事件上报,发现了各种 request:fail 错误。 特此开帖,希望官方能给一些说明,和解决方案。同时看看各位同学有没有碰到,或者已经解决了,poll 来供大家参考. 其中request:fail time out 的频率比较频繁,影响用户体验。超时时间如下: "networkTimeout": { "request": 10000, "downloadFile": 10000 }, 图片被压缩,而且无法查看原图,大家将就着看吧 [图片] [图片]
2018-04-08 - 浅谈前端/软件工程师的代码素养
“程序是写给人读的,只是偶尔让计算机执行一下。” ——Donald Ervin Knuth(高德纳) 关于代码素养 我们常常谈到“素养”一词,是指个人在专业领域内实践训练而成的一种修养,在不同的领域中有不同的体现,如在音乐领域中,“音乐素养”是指个人对于音乐的感觉程度,对音高节奏的把控,对不同流派音乐的鉴赏能力等,而在编程领域,也有不同的素养,反映出对基本功、代码整洁度、专业态度等等方面,所谓“代码素养”,简单来说,就是指代码写的是否优雅美观可维护。 绝对完美的代码是不存在的,代码素养并不是指完美主义。在翻译领域有“信,达,雅”的标准,“雅”之所以放在最后,是因为要达到它,需要有比较高的水准和经验积累。类比到编程领域,我们在编程时,第一时间想到的是如何将业务逻辑实现出来,而不是如何把代码优雅地写出来,所以写代码没有所谓的绝对优雅。但是,作为一名专业的前端工程师,确切的说,应该是专业的软件工程师,编写优雅的代码应当是时刻保持的追求,它更像是一个准绳,如同每个人知道自己该做什么,不该做什么,所谓原则,所谓底线,体现出所谓的[代码]“代码素养”[代码]。 破窗理论 破窗理论,原义指窗户破损了,建筑无人照管,人们放任窗户继续破损,最终自己也参与破坏活动,在外墙上涂鸦,任垃圾堆积,最后走向倾颓。 破窗理论在实际中非常容易出现,往往第一个人的代码写的不好,第二个人就会有类似“反正他已经写成这样了,那我也只能这样了”的思想,导致代码越维护越冗杂,最后一刻轰然坍塌,变成无人想去维护的垃圾。 整洁的代码 整洁的代码如同优美的散文,试想读过的一本好书,能够随着作者的笔锋跌宕起伏,充满了画面感,调动了自己的喜怒哀乐。代码虽然没有那样的高潮迭起,但整洁的代码应当充满张力,能够在某一时刻利用这种张力将情节推向高潮。 我更喜欢把写代码类比于写文章讲故事,写代码是创作的过程,作者需要将自己想表达的东西通过代码的形式展现出来,而整洁的代码如同讲故事一般,娓娓道来,引人入胜,不好的代码则让人感觉毫无头绪,通篇不知道在讲什么。 整洁代码原则 在现代化的前端开发中,有很多自动化工具可以帮助我们写出规范的代码,如[代码]eslint[代码],[代码]tslint[代码]等各种辅助校验工具,知名的规范如[代码]google规范[代码]、[代码]airbnb规范[代码]等等也从各个细节方面约束,帮助我们形成合理规范的代码风格。 本小节不再重复语言层面的代码风格,根据实际重构项目,总结出一系列开发过程中需要时刻注意的原则,按照重要程度优先级排列。 1. DRY(Don’t Repeat Yourself) 相信作为一名软件工程师,大家都听说过最基本的DRY原则,很多设计模式,包括面向对象本身,都是在这条原则上做努力。 DRY顾名思义,是指“不要重复自己”,它实际上强调了一个抽象性原则,如果同样或类似的代码片段出现了两次以上,那么应该将它抽象成一个通用方法或文件,在需要使用的地方去依赖引入,确保在改动的时候,只需调整一处,所有的地方都改变过来,而不是到每个地方去找到相应的代码来修改。 在实际工作中,我见过两种在这条原则上各自走向极端的代码: 一种是完全没有抽象概念,重复的代码散落在各处,更奇葩的是,有一部分的抽象,但更多的是重复,比如在common下抽取了一个[代码]data.js[代码]的数据处理文件,部分页面中引用了该文件,而更多页面完全拷贝了该文件中的几个不同方法代码。而作者的意图则是令人啼笑皆非——只用到小部分代码,没必要引入那么整个文件。且不论现代化的前端构建层面可以解决这个问题,即使是引入了整个大文件,这部分多余的代码在gzip之后也不会损失多少性能,但这种到处copy的行为带来后续的维护成本是翻倍的。 对于这种行为还遇到另外一个理由,就是工期时间短,改不动之前的代码,怕造成外网问题,那就拷贝一份相同的逻辑来修改。比如支付逻辑,原有的逻辑为单独的UI浮层+单个支付购买,现在产品提出“打包购买”需求,原有的代码逻辑又比较复杂,出现了“改不动”的现象,于是把UI层和购买逻辑的几个文件整个拷贝过来,修改几下,形成了新的“打包购买”模块,后来产品又提出“按条购买”,按照上述”改不动“原则,又拷贝了一份“按条购买”的模块。这样一来调用处的逻辑就会冗余重复,需要根据不同的购买方式引入不同UI组件和支付逻辑,另外如果新添需求,如支持“分期付款”,那么将改动的是非常多的文件,最可悲的是,最后想要把代码重构为一处统一调用的人,将会面对三份“改不动”的压力,需要众多逻辑中对比分析提取相同之处,工作量已经不能用翻倍来衡量,而这种工作量往往无法得到产品的认同和理解。 另一种极端是过度设计,在写每个逻辑的时候都去抽象,让代码的可读性大大下降,一个简单的for循环都要复用,甚至变量定义,这种代码维护起来也是比较有成本的,还有将迥然不同的逻辑过度抽象,使得抽象方法变得非常复杂,经常“牵一发而动全身”,这种行为也是不可取的。 这也是将该原则排在首位的原因,这种行为导致的重构工作量是最大的,保持良好的代码维护性是一种素养,更是一种责任,如果自己在这方面逃避或偷懒,将把这块工作量翻倍地加在将来别人或自己的身上。 2. SRP(Single Responsibility Principle) SRP也是一个比较著名的设计原则——单一职责,在面向对象的编程中,认为类应该具有单一职责,一个类的改变动机应当只有一个。 对于前端开发来说,最需要贯彻的思想是函数应当保持单一职责,一个函数应当只做一件事,这样一来是保证函数的可复用性,更单一的函数有更强的复用性,二来可以让整体的代码框架更加清晰,细节都封装在一个个小函数中。另外一点也和单一职责有关,就是无副作用的函数,也称纯函数,我们应当尽量保证纯函数的数量,非纯函数是不可避免的,但应当尽量减少它。 把SRP原则排在第二位,因为它非常的重要,没有人愿意看一团乱麻的逻辑,在维护代码时,如果没有一个清晰的逻辑结构,所有的数据定义、数据处理、DOM操作等等一系列细节的代码全部放在一个函数中,导致这个函数非常的冗长,让人本能地产生心理排斥,不愿去查看内部的逻辑。 所有的复杂逻辑放在一个函数中,相信大家看到这样的代码都会眉头一皱: [代码]show: function(a, b) { if (!isInit) { init(); isInit = true; } // reset this.balance = 0; this.isAllBalance = false; var shouldShowLayer = true, preSelectedTermId = 0, needAddress = course.address_state, showTerms, termsObj; var hasPunish = false; this.course = course = course || {}; opt = opt || {}; opt.showMax = opt.showMax || 6; (isIosApp || b.isIAP) && (usekedian = !0, priceSymbol = '<i class="icon-font i-kedian"></i>'), f.splice(b.showMax), layer.show({ $container:b.$container, content:termSelectorTpl({ terms:f, curTermId:b.curTermId || d, name:a.name, hasPunish:h, userInfo:j }, { renderTime:T.render.time.renderCourseTime, renderCourseTime:renderCourseTime, hideUserInfo:b.hideUserInfo, hideTitle:b.hideTitle, hidePayPrice:b.hidePayPrice, confirmText:b.confirmText, sys_time:a.sys_time }), cls:"term-select-new", allowMove:function(a) { return opt.allowMove || ($target.closest('.select-content').length && $('.term-select-new .select-time').height() + $('.term-select-new .select-address').height() + $('.term-select-new .select-discounts').height() > (winWidth > 360 ? 190 : winWidth > 320 ? 175 : 150)); }, afterInit:function(c) { if (needAddress) { that.loadAddress(); // 如果需要地址,且是 app 的话,屏幕可见性切换时需要更新下地址 if (isApp) { $(document).on(visibilityChange, function (e) { // console.log('visibilityChange',document[hidden]); if (!document[hidden]) { // true 参数表示必须刷新 that.loadAddress(true); } }); } } that.afterTermSelect(); $dom.on('click', '.layer-close', function() { setTimeout(function() { !opt.noAutoHide && layer.hide(); }, 100); opt.onCancel && opt.onCancel(); }); $dom.on('click', '.term', function(e) { var $this = $(this); var $terms = $('.term'); if (!$this.hasClass('disabled')) { $terms.removeClass('selected'); $this.addClass('selected'); } that.afterTermSelect(); }); $dom.on('click', '.layer-comfirm', function(e) { var $this = $(this); var termId = $dom.find('.term.selected').data('term-id'); var termName = $dom.find('.term.selected').find('.term-title').html(); var discountId = $dom.find('.discounts-list_item.selected').data('discount-id'); var couId = $dom.find('.discounts-list_item.selected .discounts-coupon').data('cou-id'); var directPay = false; // ios 手Q IAP if (that.toRecharge) { // 需要充值的金额数目 var toRechargePrice = that.curPrice - that.balance; if (isIosApp) { require.async('api', function (api) { api.invoke('api', 'balanceRecharge', { amount: toRechargePrice }); // 充值完成设置回调 api.addEventListener('balanceRechargeCallBack', function(data) { // 支付成功的话 // code=0为成功,其他表示失败 // mode=1表示走充值档位回调,2表示直接充值回调,如果ios 直接充值成功则直接支付 var directPay = data.code === 0 && data.mode === 2; // 执行回调刷新数据 that.toGetBalance(that.course, termId, function() { directPay && $this.trigger('click'); }); }); }); } else { var toRechargePrice = that.curPrice - that.balance; if (that.rechargeMap && Object.keys(that.rechargeMap).indexOf("" + toRechargePrice) > -1) { that.opt.onComfirmClick && that.opt.onComfirmClick(1); iosPay.iosRecharge({ productId: that.rechargeMap[toRechargePrice], count: toRechargePrice, succ: function() { that.toGetBalance(that.course, $('.term.selected').data('term-id')); } }); } else { that.opt.onComfirmClick && that.opt.onComfirmClick(2); // T.jump('/iosRecharge.html?_bid=167&_wv=2147483651'); that.jumpPage('/iosRecharge.html?_bid=167&_wv=2147483651'); } } return; } if (!termId) { require.async(['modules/tip/tip'], function(Tip) { Tip.show(opt.dialogTitle); }); return true; } // check address if (needAddress && !that.addressid) { if (course.must_fill_mailing || !$dom.find('.select-address').hasClass('z-no')) { // 没填地址的话地址框要标红,然后需要滑到视窗让用户看到 var $cnt = $dom.find('.select-content'); var $addressWrap = $dom.find('.select-address_wrapper').addClass('z-err'); var cntRect = $cnt[0].getBoundingClientRect(); var addressBoxRect = $addressWrap[0].getBoundingClientRect(); // console.log('>>>>> ', cntRect, addressBoxRect); if (addressBoxRect.bottom > cntRect.bottom) { $cnt.scrollTop($cnt.scrollTop() + (addressBoxRect.bottom - cntRect.bottom)); } return; } } if (that.isAllBalance && that.opt.onComfirmClick) { that.opt.onComfirmClick(3); } opt.cb && opt.cb(termId, discountId, couId, termName, that.isAllBalance, that.payBalance, that.addressid); setTimeout(function() { !opt.noAutoHide && layer.hide(); }, 300); }); $dom.on('click', '.discounts-list_item', function(e) { var $this = $(this); var $discounts = $('.discounts-list_item'); var isSelected = $this.hasClass('selected'); if (!$this.hasClass('disabled')) { $discounts.removeClass('selected'); $this.addClass(isSelected ? '' : 'selected'); that.setPayPrice(); } }); $dom.on('click', '.address-person .i-edit2, .address-add', function() { var termId = $dom.find('.term.selected').data('term-id'); var courseId = that.course.cid; var src = '/addrEdit.html?_bid=167&_wv=2147483649&ns=1&fr=' + (location.pathname.indexOf('allCourse.html') > -1 ? 4 : location.pathname.indexOf('courseDetail.html') > -1 ? 2 : 3) + '&course_id=' + courseId + '&term_id=' + termId; // T.jump(src); that.jumpPage(src); }).on('click', '.select-address_title .i-right-light', function(e) { var $addressDom = $dom.find('.select-address'); var isOpen = !$addressDom.hasClass('z-no'); if (isOpen) { $addressDom.addClass('z-no'); that.theAddressid = that.addressid; that.addressid = undefined; } else { $addressDom.removeClass('z-no'); that.addressid = that.theAddressid; } }); } }); } else { opt.cb && opt.cb(opt.curTermId || preSelectedTermId); } } [代码] 单一职责并不一定要通过很多函数来完成,也可以用分段达到目的,如同这样: [代码]show(data) { data && this.setData(data); const renderData = { data: this.data, courseData: this.data.courseData, termList: this.termList, userInfo: this.userInfo, addrList: this.addrList, isIAP: this.isIAP, balance: betterDisplayNum(this.balance), curPrice: betterDisplayNum(this.curPrice), curTermId: this.curTermId, discountList: this.discountList, curDisId: this.curDisId, jdSelectId: this.jdSelectId, curAddrId: this.curAddrId }; const formatters = { // formatters termFormatter, priceFormatter, okBtnFormatter, balanceFormatter, priceFormatterWithDiscount }; console.log('[render data]: ', renderData); const html = payLayerTpl(renderData, formatters); // 记录滚动条位置 this._setScrollTop(); // 防止重复append if (this.$view) { this.$view.replaceWith(html); } else { this.$container.append(html); } afterUIRender(() => { this.$view = $('.' + COMPONENT_NAME).show(); this._setContentHeight(); // 动态设置滚动区域的高度 this._restoreScrollTop(); // 恢复滚动位置 this._initEvent(); this._initCountDown(); // 限时折扣倒计时 }); } [代码] 虽然这个函数也没有维持单一职责,但通过“分段”的形式清晰的表明了内部的流程逻辑,这样的代码看起来就会比所有细节揉在一个函数中好很多。 对于单一职责来说,保持起来还是比较困难的,主要在于职责的拆分,有时过于细致的职责拆分也会给阅读带来比较大的困难,对于这种情况,还是拿写作来对比,单一职责相当于文章的一个“段落”,对于文章来说,每个段落都有它的中心思想,可以用一句话描述出来,如果你发现函数的中心思想很模糊,或者需要很多语言去描述它,那也许它已经有很多个职责该拆分了。 3. LKP(Least Knowledge Principle) LKP原则是最小知识原则,又称“迪米特”法则,也就是说,一个对象应该对另一个对象有最少的了解,你内部如何复杂都没关系,我只关心调用的地方。 保持暴露接口的简介易用性也是API设计的通用规则,在实际中发现了这样的一个UI组件: [代码]module.exports = { show: function(course, opt) { // 此处省略一堆逻辑 }, jumpPage: function(url) { // 此处省略一堆逻辑 }, afterTermSelect: function() { // 此处省略一堆逻辑 }, setPrice: function() { // 此处省略一堆逻辑 }, setBalance: function() { // 此处省略一堆逻辑 }, toGetBalance: function(course, curTermId, cb) { // 此处省略一堆逻辑 }, setDiscounts: function(course, curTermId, curPrice) { // 此处省略一堆逻辑 }, filterDiscounts: function(discounts, curPrice) { // 此处省略一堆逻辑 }, isSuitCoupon: function(cou, curPrice) { // 此处省略一堆逻辑 }, setPayPrice: function() { // 此处省略一堆逻辑 }, setTermTips: function(wording) { // 此处省略一堆逻辑 }, loadAddress: function(needUpdate) { // 此处省略一堆逻辑 }, setAddress: function(addressid) { // 此处省略一堆逻辑 } } [代码] 这个UI组件暴露了非常多的方法,有业务逻辑,有视图逻辑,还有工具方法,这时会给维护者带来比较大的困扰,本能的以为这些暴露出去的方法都在被使用,所以想重构其中某些方法都有些不好下手,而实际上,外部调用的方法仅仅是[代码]show[代码]而已。 一个好的封装,无论内部多么复杂,它暴露出来的一定是最简洁实用的接口,而内部逻辑是独立维护的,如上述代码,作为一个UI组件来说,提供最基本的[代码]show/hide[代码]方法即可,有必要时可加入[代码]update[代码]方法自更新,而无需暴露众多细节,造成调用者和维护者的困扰。 4. 可读性基本定理 可读性基本定理——“代码的写法应当使别人理解它所需的时间最小化”。 代码风格和原则不是一概而论的,我们经常需要对一些编码原则和方案进行取舍,例如对于三元表达式的取舍,当我们觉得两种方案都占理时,那么唯一的评判标准就是可读性基本定理,无论写法多么的高超炫技,最好的代码依旧是让人第一时间能够理解的代码。 5. 有意义的名称 代码的可读性绝大部分依赖于变量和函数的命名,一个好的名称能够一针见血地帮助维护者理解逻辑,如同写文章中的“文笔”,文笔优异者总能将故事娓娓道来,引人入胜。 不过要起好名称还是很难的,尤其是我们不是以英语为母语,更是添加了一层障碍,有些人认为纠结在名称上会导致效率变低,开发第一时间应该完成需求的开发。这样说并没有错,我们在开发过程中应当专注于功能逻辑,但不要完全忽视命名,所谓“文笔”是需要锻炼的,思考的越多,命名就会愈加的水到渠成,到后来也就不太会影响工作效率了。 在这里推荐鲍勃大叔提到的童子军规,每一次看自己的代码,都进行一次重构,最简单的重构便是改名,也许一开始觉得命名还比较贴合,但逻辑越写越不符合初始的命名了,当回顾代码时,我们可以顺手对变量和方法进行重新命名,现代编辑工具也很容易做到这一点。 文不对题的命名是最可怕的,如: [代码]function checkTimeConflict(opts) { if (opts.param.passcard || (T.bom.get('autopay') && T.bom.get('term_id'))) { selectToPay({ result: {} }, opts); } else { DB.checkTimeConflict({ param: { course_id: opts.param.courseId, term_id: opts.param.termId }, succ: function(data) { selectToPay(data, opts); }, err: function(data) { dealErr(opts, data); } }); } } [代码] 这个函数被命名为[代码]check*[代码]开头的,本意是检测课程时间是否冲突,但内部逻辑却包含了支付整个流程,此时对于调用者来说,如果不去细看内部逻辑,很有可能就会错误的认为[代码]check[代码]函数没有副作用导致事故发生。 6. 适当的注释维护 注释是一个比较有争议性的话题,有人认为可读的函数变量就很清晰,不需要额外的注释,且注释有不可维护性,如: [代码]// 1-PC, 2-android手QH5, 3-android APP, 4-ios&非手QH5, 5-IOS APP var platform = isAndroidApp ? 3 : isIosApp ? 5 : 4; [代码] 实际上,这个字段的含义早已发生了改变,但由于修改者只修改了逻辑,并没有注意到这一行注释,导致这个老注释提供了错误信息,此时的注释不仅变成了无效注释,甚至会导致维护人的误解,造成bug的产生。 对于这种情况,要么维护注释,要么在注释里面注明接口文档,维护文档,在其他情况下,适当的注释是有必要的,对于复杂的逻辑,如果有一个简练的注释,对于代码可读性的帮助是极大的,但有些不必要的注释可以去掉,注释的取舍关键在于可读性基本定理,如: [代码]const filterFn = (term) => { if (rule.hideEndTerms && term.is_end) { return false; // 隐藏已结束的期 } if (rule.hideSignEndTerms && term.is_out_of_date) { return false; // 隐藏已结束报名的期 } if (rule.hideAppliedTerms && courseUtil.isTermApplied(term)) { return false; // 隐藏已报名的期 } if (rule.hideZeroAllowedTerms && courseUtil.isTermZero(term)) { return false; // 隐藏名额已满的期 } if (rule.productType === productType.PACKAGE) { return false; // 隐藏课程包的班级 } return true; }; [代码] 对于上述逻辑来说,虽然通过变量可以大致猜出功能含义,但一眼看上去就能清晰掌握逻辑结构,归功于注释的简明与清晰。 小结 本文提到的6个代码编写的原则,前三个偏向于代码维护性,后三个偏向于代码可读性,整个可维护性和可读性构成了代码的基本素养。作为一名前端开发工程师,想要拥有良好的代码素养,首先要让自己的代码可维护,不给别人的维护带来巨大的成本和工作量,其次尽量保证代码的美观可读,整洁的代码人见人爱,如同阅读一本好书,令人心情愉悦。 ”代码素养“是一种态度,真正热爱编程的程序员一定不会缺失“代码素养”。我们通常称“写代码”为[代码]“程序设计”[代码],而不是“程序编写”,“设计”一词体现出了我们的代码是一件作品,也许不如“艺术品”那么精致,但也不是什么粗麻烂布,如果在写代码时天马行空,得过且过,抱着只要能实现功能的思想,那这部“作品“是不具有观赏价值的,这不仅仅体现出代码编写者的”不专业”,更是反映出对待编程这件事的态度,代码的整洁程度、可维护性取决于你是否真正“在意”你的代码,每个程序员不一定热爱编程,但请你一定要以“认真”的态度对待自己的专业。 [代码]"clean code"[代码]的作者鲍勃大叔提到,有人曾送给他一条腕带,上面写着“Test Obsessed”,他发觉自己带上后再也无法取下了,不仅是因为腕带很紧,更是因为它也是一条精神上的紧箍咒。在编程时,我们下意识的看下自己的手腕,是否能发现一条隐形的腕带呢?
2019-03-14 - CSS3 Animation动画的十二原则
作为前端的设计师和工程师,我们用 CSS 去做样式、定位并创建出好看的网站。我们经常用 CSS 去添加页面的运动过渡效果甚至动画,但我们经常做的不过如此。 [代码] 动效是一个有助于访客和用户理解我们设计的强有力工具。这里有些原则能最大限度地应用在我们的工作中。 迪士尼经过基础工作练习的长时间累积,在 1981 年出版的 The Illusion of Life: Disney Animation 一书中发表了动画的十二个原则 ([] (https://en.wikipedia.org/wiki/12_basic_principles_of_animation)) 。这些原则描述了动画能怎样用于让观众相信自己沉浸在现实世界中。 [代码] 在本文中,我会逐个介绍这十二个原则,并讨论它们怎样运用在网页中。你能在 Codepen 找到它们[] (https://codepen.io/collection/AxKOdY/)。 挤压和拉伸 (Squash and stretch) [图片] 这是物体存在质量且运动时质量保持不变的概念。当一个球在弹跳时,碰击到地面会变扁,恢复的时间会越来越短。 [代码] 创建对象的时候最有用的方法是参照实物,比如人、时钟和弹性球。 当它和网页元件一起工作时可能会忽略这个原则。DOM 对象不一定和实物相关,它会按需要在屏幕上缩放。例如,一个按钮会变大并变成一个信息框,或者错误信息会出现和消失。 尽管如此,挤压和伸缩效果可以为一个对象增加实物的感觉。甚至一些形状上的小变化就可以创造出细微但抢眼的效果。 HTML [代码] [代码] <h1>Principle 1: Squash and stretch</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle one"> <div class="shape"></div> <div class="surface"></div> </article> [代码] CSS [代码].one .shape { animation: one 4s infinite ease-out; } .one .surface { background: #000; height: 10em; width: 1em; position: absolute; top: calc(50% - 4em); left: calc(50% + 10em); } @keyframes one { 0%, 15% { opacity: 0; } 15%, 25% { transform: none; animation-timing-function: cubic-bezier(1,-1.92,.95,.89); width: 4em; height: 4em; top: calc(50% - 2em); left: calc(50% - 2em); opacity: 1; } 35%, 45% { transform: translateX(8em); height: 6em; width: 2em; top: calc(50% - 3em); animation-timing-function: linear; opacity: 1; } 70%, 100% { transform: translateX(8em) translateY(5em); height: 6em; width: 2em; top: calc(50% - 3em); opacity: 0; } } body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 预备动作 (Anticipation) [图片] 运动不倾向于突然发生。在现实生活中,无论是一个球在掉到桌子前就开始滚动,或是一个人屈膝准备起跳,运动通常有着某种事先的累积。 [代码] 我们能用它去让我们的过渡动画显得更逼真。预备动作可以是一个细微的反弹,帮人们理解什么对象将在屏幕中发生变化并留下痕迹。 例如,悬停在一个元件上时可以在它变大前稍微缩小,在初始列表中添加额外的条目来介绍其它条目的移除方法。 [代码] HTML [代码]<h1>Principle 2: Anticipation</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle two"> <div class="shape"></div> <div class="surface"></div> </article> [代码] CSS [代码].two .shape { animation: two 5s infinite ease-out; transform-origin: 50% 7em; } .two .surface { background: #000; width: 8em; height: 1em; position: absolute; top: calc(50% + 4em); left: calc(50% - 3em); } @keyframes two { 0%, 15% { opacity: 0; transform: none; } 15%, 25% { opacity: 1; transform: none; animation-timing-function: cubic-bezier(.5,.05,.91,.47); } 28%, 38% { transform: translateX(-2em); } 40%, 45% { transform: translateX(-4em); } 50%, 52% { transform: translateX(-4em) rotateZ(-20deg); } 70%, 75% { transform: translateX(-4em) rotateZ(-10deg); } 78% { transform: translateX(-4em) rotateZ(-24deg); opacity: 1; } 86%, 100% { transform: translateX(-6em) translateY(4em) rotateZ(-90deg); opacity: 0; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 演出布局 (Staging) [图片] 演出布局是确保对象在场景中得以聚焦,让场景中的其它对象和视觉在主动画发生的地方让位。这意味着要么把主动画放到突出的位置,要么模糊其它元件来让用户专注于看他们需要看的东西。 [代码] 在网页方面,一种方法是用 model 覆盖在某些内容上。在现有页面添加一个遮罩并把那些主要关注的内容前置展示。 另一种方法是用动作。当很多对象在运动,你很难知道哪些值得关注。如果其它所有的动作停止,只留一个在运动,即使动得很微弱,这都可以让对象更容易被察觉。 [代码] 还有一种方法是做一个晃动和闪烁的按钮来简单地建议用户比如他们可能要保存文档。屏幕保持静态,所以再细微的动作也会突显出来。 HTML [代码]<h1>Principle 3: Staging</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle three"> <div class="shape a"></div> <div class="shape b"></div> <div class="shape c"></div> </article> [代码] CSS [代码].three .shape.a { transform: translateX(-12em); } .three .shape.c { transform: translateX(12em); } .three .shape.b { animation: three 5s infinite ease-out; transform-origin: 0 6em; } .three .shape.a, .three .shape.c { animation: threeb 5s infinite linear; } @keyframes three { 0%, 10% { transform: none; animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 26%, 30% { transform: rotateZ(-40deg); } 32.5% { transform: rotateZ(-38deg); } 35% { transform: rotateZ(-42deg); } 37.5% { transform: rotateZ(-38deg); } 40% { transform: rotateZ(-40deg); } 42.5% { transform: rotateZ(-38deg); } 45% { transform: rotateZ(-42deg); } 47.5% { transform: rotateZ(-38deg); animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 58%, 100% { transform: none; } } @keyframes threeb { 0%, 20% { filter: none; } 40%, 50% { filter: blur(5px); } 65%, 100% { filter: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 连续运动和姿态对应 (Straight-Ahead Action and Pose-to-Pose) [图片] 连续运动是绘制动画的每一帧,姿态对应是通常由一个 assistant 在定义一系列关键帧后填充间隔。 [代码] 大多数网页动画用的是姿态对应:关键帧之间的过渡可以通过浏览器在每个关键帧之间的插入尽可能多的帧使动画流畅。 [代码] 有一个例外是定时功能step。通过这个功能,浏览器 “steps” 可以把尽可能多的无序帧串清晰。你可以用这种方式绘制一系列图片并让浏览器按顺序显示出来,这开创了一种逐帧动画的风格。 HTML [代码]<h1>Principle 4: Straight Ahead Action and Pose to Pose</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle four"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].four .shape.a { left: calc(50% - 8em); animation: four 6s infinite cubic-bezier(.57,-0.5,.43,1.53); } .four .shape.b { left: calc(50% + 8em); animation: four 6s infinite steps(1); } @keyframes four { 0%, 10% { transform: none; } 26%, 30% { transform: rotateZ(-45deg) scale(1.25); } 40% { transform: rotateZ(-45deg) translate(2em, -2em) scale(1.8); } 50%, 75% { transform: rotateZ(-45deg) scale(1.1); } 90%, 100% { transform: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 跟随和重叠动作 (Follow Through and Overlapping Action) [图片] 事情并不总在同一时间发生。当一辆车从急刹到停下,车子会向前倾、有烟从轮胎冒出来、车里的司机继续向前冲。 [代码] 这些细节是跟随和重叠动作的例子。它们在网页中能被用作帮助强调什么东西被停止,并不会被遗忘。例如一个条目可能在滑动时稍滑微远了些,但它自己会纠正到正确位置。 要创造一个重叠动作的感觉,我们可以让元件以稍微不同的速度移动到每处。这是一种在 iOS 系统的视窗 (View) 过渡中被运用得很好的方法。一些按钮和元件以不同速率运动,整体效果会比全部东西以相同速率运动要更逼真,并留出时间让访客去适当理解变化。 [代码] 在网页方面,这可能意味着让过渡或动画的效果以不同速度来运行。 HTML [代码]<h1>Principle 5: Follow Through and Overlapping Action</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle five"> <div class="shape-container"> <div class="shape"></div> </div> </article> [代码] CSS [代码].five .shape { animation: five 4s infinite cubic-bezier(.64,-0.36,.1,1); position: relative; left: auto; top: auto; } .five .shape-container { animation: five-container 4s infinite cubic-bezier(.64,-0.36,.1,2); position: absolute; left: calc(50% - 4em); top: calc(50% - 4em); } @keyframes five { 0%, 15% { opacity: 0; transform: translateX(-12em); } 15%, 25% { transform: translateX(-12em); opacity: 1; } 85%, 90% { transform: translateX(12em); opacity: 1; } 100% { transform: translateX(12em); opacity: 0; } } @keyframes five-container { 0%, 35% { transform: none; } 50%, 60% { transform: skewX(20deg); } 90%, 100% { transform: none; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 缓入缓出 (Slow In and Slow Out) [图片] 对象很少从静止状态一下子加速到最大速度,它们往往是逐步加速并在停止前变慢。没有加速和减速,动画感觉就像机器人。 [代码] 在 CSS 方面,缓入缓出很容易被理解,在一个动画过程中计时功能是一种描述变化速率的方式。 [代码] 使用计时功能,动画可以由慢加速 (ease-in)、由快减速 (ease-out),或者用贝塞尔曲线做出更复杂的效果。 HTML [代码]<h1>Principle 6: Slow in and Slow out</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle six"> <div class="shape a"></div> </article> [代码] CSS [代码].six .shape { animation: six 3s infinite cubic-bezier(0.5,0,0.5,1); } @keyframes six { 0%, 5% { transform: translate(-12em); } 45%, 55% { transform: translate(12em); } 95%, 100% { transform: translate(-12em); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 弧线运动 (Arc) [图片] 虽然对象是更逼真了,当它们遵循「缓入缓出」的时候它们很少沿直线运动——它们倾向于沿弧线运动。 我们有几种 CSS 的方式来实现弧线运动。一种是结合多个动画,比如在弹力球动画里,可以让球上下移动的同时让它右移,这时候球的显示效果就是沿弧线运动。 HTML [代码]<h1>Principle 7: Arc (1)</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle sevena"> <div class="shape-container"> <div class="shape a"></div> </div> </article> [代码] CSS [代码].sevena .shape-container { animation: move-right 6s infinite cubic-bezier(.37,.55,.49,.67); position: absolute; left: calc(50% - 4em); top: calc(50% - 4em); } .sevena .shape { animation: bounce 6s infinite linear; border-radius: 50%; position: relative; left: auto; top: auto; } @keyframes move-right { 0% { transform: translateX(-20em); opacity: 1; } 80% { opacity: 1; } 90%, 100% { transform: translateX(20em); opacity: 0; } } @keyframes bounce { 0% { transform: translateY(-8em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 15% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 25% { transform: translateY(-4em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 32.5% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 40% { transform: translateY(0em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 45% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 50% { transform: translateY(3em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 56% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 60% { transform: translateY(6em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 64% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } 66% { transform: translateY(7.5em); animation-timing-function: cubic-bezier(.51,.01,.79,.02); } 70%, 100% { transform: translateY(8em); animation-timing-function: cubic-bezier(.19,1,.7,1); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] [图片] 另外一种是旋转元件,我们可以设置一个在对象之外的原点来作为它的旋转中心。当我们旋转这个对象,它看上去就是沿着弧线运动。 HTML [代码]<h1>Principle 7: Arc (2)</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle sevenb"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].sevenb .shape.a { animation: sevenb 3s infinite linear; top: calc(50% - 2em); left: calc(50% - 9em); transform-origin: 10em 50%; } .sevenb .shape.b { animation: sevenb 6s infinite linear reverse; background-color: yellow; width: 2em; height: 2em; left: calc(50% - 1em); top: calc(50% - 1em); } @keyframes sevenb { 100% { transform: rotateZ(360deg); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 次要动作 (Secondary Action) [图片] 虽然主动画正在发生,次要动作可以增强它的效果。这就好比某人在走路的时候摆动手臂和倾斜脑袋,或者弹性球弹起的时候扬起一些灰尘。 在网页方面,当主要焦点出现的时候就可以开始执行次要动作,比如拖拽一个条目到列表中间。 HTML [代码]<h1>Principle 8: Secondary Action</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle eight"> <div class="shape a"></div> <div class="shape b"></div> <div class="shape c"></div> </article> [代码] CSS [代码].eight .shape.a { transform: translateX(-6em); animation: eight-shape-a 4s cubic-bezier(.57,-0.5,.43,1.53) infinite; } .eight .shape.b { top: calc(50% + 6em); opacity: 0; animation: eight-shape-b 4s linear infinite; } .eight .shape.c { transform: translateX(6em); animation: eight-shape-c 4s cubic-bezier(.57,-0.5,.43,1.53) infinite; } @keyframes eight-shape-a { 0%, 50% { transform: translateX(-5.5em); } 70%, 100% { transform: translateX(-10em); } } @keyframes eight-shape-b { 0% { transform: none; } 20%, 30% { transform: translateY(-1.5em); opacity: 1; animation-timing-function: cubic-bezier(.57,-0.5,.43,1.53); } 32% { transform: translateY(-1.25em); opacity: 1; } 34% { transform: translateY(-1.75em); opacity: 1; } 36%, 38% { transform: translateY(-1.25em); opacity: 1; } 42%, 60% { transform: translateY(-1.5em); opacity: 1; } 75%, 100% { transform: translateY(-8em); opacity: 1; } } @keyframes eight-shape-c { 0%, 50% { transform: translateX(5.5em); } 70%, 100% { transform: translateX(10em); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 时间节奏 (Timing) [图片] 动画的时间节奏是需要多久去完成,它可以被用来让看起来很重的对象做很重的动画,或者用在添加字符的动画中。 [代码] 这在网页上可能只要简单调整 animation-duration 或 transition-duration 值。 [代码] 这很容易让动画消耗更多时间,但调整时间节奏可以帮动画的内容和交互方式变得更出众。 HTML [代码]<h1>Principle 9: Timing</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle nine"> <div class="shape a"></div> <div class="shape b"></div> </article> [代码] CSS [代码].nine .shape.a { animation: nine 4s infinite cubic-bezier(.93,0,.67,1.21); left: calc(50% - 12em); transform-origin: 100% 6em; } .nine .shape.b { animation: nine 2s infinite cubic-bezier(1,-0.97,.23,1.84); left: calc(50% + 2em); transform-origin: 100% 100%; } @keyframes nine { 0%, 10% { transform: translateX(0); } 40%, 60% { transform: rotateZ(90deg); } 90%, 100% { transform: translateX(0); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 夸张手法 (Exaggeration) [图片] 夸张手法在漫画中是最常用来为某些动作刻画吸引力和增加戏剧性的,比如一只狼试图把自己的喉咙张得更开地去咬东西可能会表现出更恐怖或者幽默的效果。 在网页中,对象可以通过上下滑动去强调和刻画吸引力,比如在填充表单的时候生动部分会比收缩和变淡的部分更突出。 HTML [代码]<h1>Principle 10: Exaggeration</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle ten"> <div class="shape"></div> </article> [代码] CSS [代码].ten .shape { animation: ten 4s infinite linear; transform-origin: 50% 8em; top: calc(50% - 6em); } @keyframes ten { 0%, 10% { transform: none; animation-timing-function: cubic-bezier(.87,-1.05,.66,1.31); } 40% { transform: rotateZ(-45deg) scale(2); animation-timing-function: cubic-bezier(.16,.54,0,1.38); } 70%, 100% { transform: rotateZ(360deg) scale(1); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 扎实的描绘 (Solid drawing) [图片] 当动画对象在三维中应该加倍注意确保它们遵循透视原则。因为人们习惯了生活在三维世界里,如果对象表现得与实际不符,会让它看起来很糟糕。 如今浏览器对三维变换的支持已经不错,这意味着我们可以在场景里旋转和放置三维对象,浏览器能自动控制它们的转换。 HTML [代码]<h1>Principle 11: Solid drawing</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle eleven"> <div class="shape"> <div class="container"> <span class="front"></span> <span class="back"></span> <span class="left"></span> <span class="right"></span> <span class="top"></span> <span class="bottom"></span> </div> </div> </article> [代码] CSS [代码].eleven .shape { background: none; border: none; perspective: 400px; perspective-origin: center; } .eleven .shape .container { animation: eleven 4s infinite cubic-bezier(.6,-0.44,.37,1.44); transform-style: preserve-3d; } .eleven .shape span { display: block; position: absolute; opacity: 1; width: 4em; height: 4em; border: 1em solid #fff; background: #2d97db; } .eleven .shape span.front { transform: translateZ(3em); } .eleven .shape span.back { transform: translateZ(-3em); } .eleven .shape span.left { transform: rotateY(-90deg) translateZ(-3em); } .eleven .shape span.right { transform: rotateY(-90deg) translateZ(3em); } .eleven .shape span.top { transform: rotateX(-90deg) translateZ(-3em); } .eleven .shape span.bottom { transform: rotateX(-90deg) translateZ(3em); } @keyframes eleven { 0% { opacity: 0; } 10%, 40% { transform: none; opacity: 1; } 60%, 75% { transform: rotateX(-20deg) rotateY(-45deg) translateY(4em); animation-timing-function: cubic-bezier(1,-0.05,.43,-0.16); opacity: 1; } 100% { transform: translateZ(-180em) translateX(20em); opacity: 0; } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码] 吸引力 (Appeal) [图片] 吸引力是艺术作品的特质,让我们与艺术家的想法连接起来。就像一个演员身上的魅力,是注重细节和动作相结合而打造吸引性的结果。 [代码] 精心制作网页上的动画可以打造出吸引力,例如 Stripe 这样的公司用了大量的动画去增加它们结账流程的可靠性。 [代码] HTML [代码]<h1>Principle 12: Appeal</h1> <h2><a href="https://cssanimation.rocks/principles/" target="_parent">Animation Principles for the Web</h2> <article class="principle twelve"> <div class="shape"> <div class="container"> <span class="item one"></span> <span class="item two"></span> <span class="item three"></span> <span class="item four"></span> </div> </div> </article> [代码] CSS [代码].twelve .shape { background: none; border: none; perspective: 400px; perspective-origin: center; } .twelve .shape .container { animation: show-container 8s infinite cubic-bezier(.6,-0.44,.37,1.44); transform-style: preserve-3d; width: 4em; height: 4em; border: 1em solid #fff; background: #2d97db; position: relative; } .twelve .item { background-color: #1f7bb6; position: absolute; } .twelve .item.one { animation: show-text 8s 0.1s infinite ease-out; height: 6%; width: 30%; top: 15%; left: 25%; } .twelve .item.two { animation: show-text 8s 0.2s infinite ease-out; height: 6%; width: 20%; top: 30%; left: 25%; } .twelve .item.three { animation: show-text 8s 0.3s infinite ease-out; height: 6%; width: 50%; top: 45%; left: 25%; } .twelve .item.four { animation: show-button 8s infinite cubic-bezier(.64,-0.36,.1,1.43); height: 20%; width: 40%; top: 65%; left: 30%; } @keyframes show-container { 0% { opacity: 0; transform: rotateX(-90deg); } 10% { opacity: 1; transform: none; width: 4em; height: 4em; } 15%, 90% { width: 12em; height: 12em; transform: translate(-4em, -4em); opacity: 1; } 100% { opacity: 0; transform: rotateX(-90deg); width: 4em; height: 4em; } } @keyframes show-text { 0%, 15% { transform: translateY(1em); opacity: 0; } 20%, 85% { opacity: 1; transform: none; } 88%, 100% { opacity: 0; transform: translateY(-1em); animation-timing-function: cubic-bezier(.64,-0.36,.1,1.43); } } @keyframes show-button { 0%, 25% { transform: scale(0); opacity: 0; } 35%, 80% { transform: none; opacity: 1; } 90%, 100% { opacity: 0; transform: scale(0); } } /* General styling */ body { margin: 0; background: #e9b59f; font-family: HelveticaNeue, Arial, Sans-serif; color: #fff; } h1 { position: absolute; top: 0; left: 0; right: 0; text-align: center; font-weight: 300; } h2 { font-size: 0.75em; position: absolute; bottom: 0; left: 0; right: 0; text-align: center; } a { text-decoration: none; color: #333; } .principle { width: 100%; height: 100vh; position: relative; } .shape { background: #2d97db; border: 1em solid #fff; width: 4em; height: 4em; position: absolute; top: calc(50% - 2em); left: calc(50% - 2em); } [代码]
2019-03-21