- 【高校开发者】简单分享下
简单分享下 写这篇文章主要是想跟刚迈入编程这条水泥路的小程序新手开发人员一起交流交流的,因为咱也只是个开发小程序的菜鸟人员,技术帖咱是写不来了,就只能跟大家唠唠嗑了。 1. 开篇 接触微信小程序也有一年多了,在这一年多的时间里也写过五、六个小程序,最初接触小程序是因为我们想通过小程序,来推广运营公众号和扩展公众号功能。 之后的开发就完全是因为好玩和兴趣了吧! 好玩??? 怎么玩? 大一大二咱大家刚接触算法编程,或许对算法编程是这样的: [图片] [图片] [图片] [图片] 而且经常会听到学这到底干什么用的呀! 好烦,这周数据结构又学了几个算法,二叉树?图?那行吧,咱就用这些个数据结构写个小程序吧! 又或者我们玩了一些好玩的小程序,那行吧,咱也试着用我们学过的算法知识在小程序上也写一个吧? 即使是些老掉牙的游戏,贪吃蛇、扫雷、连连看消消乐、成语接龙等等这些小游戏,实现后你还能在这些个游戏基础上发挥你的脑洞,进行创作改编。或许还能让你编个爆款小程序出来也说不定,是吧。 这些小游戏,小程序内部或多或少都用到了传说中的算法。 之所以选择用小程序来实现,还有一个最主要的原因就是小程序能够很轻易、很低调的就让我们秀一波。 小程序在真机上运行测试很容易,一个二维码就搞定,而且开发的小程序,不仅仅只是让我们本专业同学能够测测玩玩,还能够让更多非本专业的,好比你对象呀同学呀爸爸妈妈呀爷爷奶奶呀哥哥姐姐弟弟妹妹姑姑婶婶阿姨婆婆呀等等等等(只要他们有微信就行)都能够一起体验我们写的这些个小游戏呀。(o(∩_∩)o ) 所以在小程序开发过程中不仅学到了知识,还让我们见到了知识,玩到了知识,还能从中时不时的得到些个赞赏鼓励。你说这样子学习,你学不好才怪! 小程序还可以是一个表白神器,花点心思写一个专属 Ta 的独一无二的小程序,只给你的 Ta 开放个权限,再送给 Ta 比你花多少钱买个什么礼物都珍贵,你说我说的在理不!所以,你说小程序开发好不好,爱情学习双丰收呀。 2. 案例 好吧,上面废话有点多,下面先放几个自己做的小程序聊聊,素材什么的都是自己花心思画的(丑不丑无所谓,自己看着舒服就行),而且写一款完完全全凭自己感觉设计出来的小程序会让自己更有成就感。 2.1 反义词消消乐 反义词消消乐这个就是去年参加微信小程序应用开发赛做的一个小程序,为了增加游戏趣味性也增加了多人对战的功能。 反义词的逻辑匹配算法其实很简单,后台字词数据表,表中每条数据存储正、反两词,因此拥有共同的id值(int类型),前端获取到数据后只需将每条数据进行简单分割处理,可以让正意思词的标记值等于+id,反意思词的标记值等于-id,这样我们就能通过两标记值相加为0,来判断字词是否匹配成功。 游戏内的所有逻辑都交给小程序来判断运转,后端只是用来实现数据的存取,中转和传输。多人中前后端是使用 WebSocket 通道进行实时双向通信的,确保游戏内所有消息能够快速同步。逻辑如下: [图片] 简单解释下上图,当用户点击匹配后,发送信号与后台建立websocket连接,连接成功后传回连接的websocket的id值即sid,前端打包用户数据,sid,用户状态,用户转态就是此时是否已经退出游戏,发送给后端,后端保存信息,放入用户匹配池中,当准备就绪后,传回前端,前端发送匹配请求,后端再次开始为用户分配房间号,之后判断实时监听判断分配的房间是否满足两人,满足即可开始游戏逻辑。当游戏开始后,前端与后端的处理逻辑为如下所示: [图片] 上图是表示游戏匹配过程中的逻辑,其中打包小程序端发送的游戏信息其实就是用户点击反义词数据的状态信息,有助于后台判断分数加减等情况。服务器端的定向广播游戏信息,就是将处理好的分数等情况广播发送给定向房间内的用户。 反义词小程序整体流程大致是这么实现,而分配玩家的逻辑可以是按随机,按先到先服务,又或者按指定条件(如胜率分段等)将玩家分配到一起。 [图片] [图片] 2.2 困住小星星 困住小星星这个小程序,其玩法是模仿日本游戏设计师TaroIto2007年制作的“黑猫”(ChatNoir)游戏,而我在将其成功围住之后放了一些彩蛋,具体内容嘛就不说了 (✿◡‿◡),你们可以自己更改主题后随意发挥。 玩法就是游戏初始化墙的个数大概是9-13个之间。玩家需自己想办法点一个圈,目的就是要将其围住,不让她从边界跑掉即可。 小星星的寻路机制算法用到了最小路径和最大通路的算法,在每次寻路前先计算出各个非墙点的最小路径步数,以及最大通路步数,当最小路径步数相同时,就用最大通路步数来进行比较,最终找到最优路径,所以要想困住其实还是有点难的。 因为游戏中的地图是9*9,因此在最短路径算法中,其中点是(4,4),要计算到各边界的最短路径问题,可以从外往里开始计算。比如最外围,因为是边界,所以其路径长度为0,之后每往里一层,其路径长度就加1,直到到达主角位置。在不存在墙的情况下如下示: [图片] 最小路径: 其实要想计算出这个图的路径,我们不难发现,计算某个点的路径时(例如上图标红处),我们只需遍历找出每个点周围(即左、左上、右上、右、右下、左下)路径最小的值并加1,即是最终的最短路径长度。 如何实现?首先我们规定图中81个点初始path值可设为 -100,障碍物的path值可设为 100,边界path值可设为 0,主角位置可设为 999。之后再分别从四个大方向,即左上、右上、右下、左下这四个方向进行反复计算。 如何计算,以左上为例,左上计算即表示从左到右,从上到下,对各个点进行遍历。在遍历各点的同时,判断该点是否为障碍物,是则直接返回path的值为100,若该点为边界,则直接返回path的值为0。 如果都不是,则找出该点周围的6个点,并分别遍历周围的这6个点,判断这6个点的路径值,找出周围6个点路径的最小值min,min初值设为100。 如果筛选出来周围的点中存在没有计算过的点,即存在小于0的值,那需要等以后来计算他们的值,因此先不参与比较判断。而周围点的path值满足大于0的条件时,与min值比较,选出其中的最小值并赋给min,遍历完6个点后。当min值满足小于100的条件时,那么说明该点周围的最小值已经计算出来,直接将该点周围最小值路径值加1即为该点path值。 上面说的有点绕,直接上代码: [代码]calPath: function(location) { //计算路径 var row = location.row var col = location.col if (this.data.map_location[row][col] == 1) { //墙直接返回100 location.path = 100 return location.path } if (row == 0 || col == 0 || row == 8 || col == 8) { //边界直接返回0 location.path = 0 return location.path } var sixDir = this.calSixDir(location) //寻找周围的六个点 //遍历周围6个点,找出最小值 var min = 100 for (var i = 0; i < 6; i++) { if (sixDir[i].path >= 0) { // 存在点没有值,那需要等下次再来计算其值 var tmp = sixDir[i].path if (min > tmp) { min = tmp } } } if (min < 100) { location.path = min + 1 } else { location.path += 1 } return location.path }, [代码] 以此类推,从右上、右下、左下这三个方向进行计算,也都是相同的逻辑操作。 最大通路: 最大通路其实与最短路径的计算差不多,只是最大通路我们计算的是该点周围能走的个数,即寻找出该点周围6个点后,统计一下该点周围非墙的个数即可。因此计算时只需两个for循环遍历一遍图中所有点并分别计算一下即可。 [图片] [图片] 2.3 fly 拖鞋、摩斯密码 这个名字嘛fly 拖鞋,就是我随便取的。当时画素材的时候,就是随笔画了画涂了涂,看着有点像拖鞋就叫了 fly 拖鞋,这个游戏其实是用小游戏写的,游戏内容其实就跟fly bird一样。 开发这个游戏的过程中其实没用到什么算法,也没什么好说的。但用到了些物理知识,就是高中咱都学过的上抛与自由落体的公式,说这个就是想你知道写游戏的时候其实很经常用到物理知识。 这个摩斯密码,就是因为当时跟那谁谁谁聊起过摩斯密码,感觉那滴滴哒哒的也很神奇,而且又凑巧那天看到了个摩斯密码表,于是就照着那个表写了这个翻译器。所以用小程序写个小工具自己用还是很方便的。我觉得吧小程序是最适合自己写工具的,方便,而且干什么都行,就看你怎么用了。 [图片] [图片] 上面说那么多其实就想让学弟学妹们知道小程序可以这么玩。把我们所学的知识用可视化的形式体现出来,这可能比单纯的学习课本上的知识更有趣。 3.聊聊 小程序开发起来不会太难,在这个过程中其实也没有遇到太多大问题,遇到的bug无非就两种,要么语法错误,要么逻辑错误。 其实当遇到一个问题的时候,最好的解决办法就是官方文档了。这个真不是废话,很多时候遇到的bug其实就是我们对api的不熟悉,错用,用错而已,再不是,那就是咱自己代码逻辑错误了吧。 3.1 开发 开发个小程序的过程无非就这五个过程:问题—>分析—>设计—> 编码—>优化。 本人开发的心路历程首先就是自己要明白到底要开发个什么东西出来,之后便是去官方文档中简单查询下这些api,看看我们设计实现的这些个功能到底可不可行能不能实现。实现不了的,就换个思路看看有什么可替代的方案。 之后就是得想清楚,具体的实现步骤,实现逻辑,并分配好前后端的功能逻辑,之后再简单搭建小程序的整体框架,架构。我写的小程序其实大部分的实现逻辑都是在前端实现,后端我只是单纯的用来做些数据存取、中转等功能。 当然现在小程序的云开发,大大方便了我们,直接可以将数据放到云开发数据库上,用官方的原话讲就是一个既可在小程序前端操作,也能在云函数中读写的 JSON 数据库,而且这也大大简化了后台服务器的环境构建,运营维护等。可以减少很大的工作量。 最后再是动手编码实现我们的想法,将小程序的基础功能逻辑基本实现后,再慢慢的对其进行优化,完善。 其实我最喜欢就是完善和优化的这个过程,它能够让我很惬意的去设计、体验,将我们自己的小程序从粗糙的demo到精致的成品这个过程,是一件很有成就感的事。作出一个美美哒,精致的小程序,你就会连吃饭学习睡觉都感觉很舒畅。 3.2 收尾 最后总结就是,在开发过程中,可以先分析好功能逻辑,再开发个拥有主要功能的demo出来(先不管页面什么的设计有多难看),之后我们在这个demo的基础上一版版的优化修改,一步步迭代,最终成型。是的,小程序的开发就是这么容易,这么简单,这么好学,这么有趣。 还有就是要开发设计一个小程序其实不需要太多太复杂的功能,开发一个小程序的真谛就是简洁! 不仅仅是界面简洁,操作简洁,其实最主要的就是功能简洁,这或许也就是小程序设计的初衷吧。 最后还有就是遇到问题咱们首先确定一下是不是语法错误,再看看是不是逻辑错误,再不是,可以去文档上看看找找原因,文档还没有就去开发者社区里搜搜,开发者社区还没有的话可以先在这里面提个问题,最后咱们再去百度google,查查看,如果还没有解决方案的话。 那没办法了,只能开大招了—— 小黄鸭调试法! [图片] 在很久很久以前,有一个传说,传说中有一位程序大师,他随身携带了一只小黄鸭,每当在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码…没了,这个传说就这么短,这就是传说中的小黄鸭调试发。 为什么呢?维基百科是这么解释的,小黄鸭调试法,又称橡皮鸭调试法,或者黄鸭除虫法,是在软件工程中使用的代码调试的一种方法,方法就是在程序调试,测试,除错过程中,操作人向小黄鸭耐心的解释每一行程序的作用,以此来激发灵感与发现矛盾 说白了就是回归带自己代码上,然后自言自语一翻。 你还别说,这个真管用,说出来更容易帮自己捋清楚思路,这个方法真的是屡试不爽,一直试一直爽! 要是你有幸能够把问题解决好了,最好可以记录一下踩过的坑那是最好的了,如果你再善良一点那就来开发者社区中分享一波吧。不管问题多简单,你要知道比你懂的少的人还是有很多的,他们那些新新手可能更需要的是咱们这些菜鸟新手的帮助吧。 最后再说一句话: [图片]
2019-05-27 - 红警ol小游戏开发小结
在10月份的时候,我们接手了一款红警ol的小游戏。其玩法是玩家选择红警ol游戏中的经典攻击武器,并且拖动武器到规定的地图部分,来抵御来自幽灵的一波波进攻,最后在小游戏结束时生成游戏过程中的武器摆放阵型图来让玩家主动转发,配合小游戏专有的关系链数据来提前预热红警ol的游戏。 [图片] 技术选型: 依附于微信的生态,小游戏一诞生就已经得到了众多国内知名的游戏开发引擎的适配,而一些国外的游戏开发框架在社区内也有针对小游戏版本的提供。红警ol小游戏选择的游戏引擎开发库是Phaser.js,一款相对小众但是在开源社区内又是比较受欢迎的游戏框架。 为什么选择Phaser.js?Phaser.js给一个刚刚接触游戏开发的新手的感觉就是,这个完全是为新手而开发的游戏框架。官网上有近700多个开发示例,而且针对不同的游戏内置对象都有相当丰富的示例介绍和可以在线调试运行环境,通过示例的源码,可以很快速的进行上手开发。 [图片] 另外Phaser.js是一个专注于2D领域的游戏引擎,其提供的功能模块非常丰富,可以很好的满足我们开发一款2D小游戏的需求。这里要提一下的是,Phaser.js提供了四种不同的物理引擎(Arcade、P2、Box2d和Najia)来帮我开发一些带物理效果的游戏,四种物理引擎在提供的功能的丰富度和性能上表现都不同,开发者可以根据自己游戏所要体现的物理效果丰富度和表现力的不同来搭配不同的游戏物理引擎。 [图片](图片来自网络) 游戏开发经验1)、在Phaser中,我们通过State来管理不同的游戏场景,通常一个游戏可以分为游戏加载场景、游戏开始场景、游戏进行中场景和游戏结束场景,而在小游戏中,我们还会多个游戏排行榜场景,主要承载一些游戏排行榜的内容和一些分享的功能。场景区分之后就是用不同的State来管理这些场景的加载和显示了。每个State都有初始化(init)、预加载(preload)、准备就绪(create)、更新周期(update)、渲染完毕(render) 五个生命周期函数,管理一个场景也就是在这些生命周期函数内执行相应的逻辑。 [图片] 2)、小游戏资源打包 在web开发中,我们通常为了提升性能而将多张小的图片合成一张雪碧图,这样可以节省大量网络请求时间。在小游戏的开发上,Phaser.js支持加载由多张图片生成的纹理图集,我们可以通过TexturePacker这个工具来帮助我们生成纹理图集,具体的使用过程这里就不阐述,网上教程一大把。 当把所有的纹理资源都打包成一张张纹理图集之后,可以进一步通过图片压缩工具去压缩图片,来达到图片加载性能的最大化。通常,我们将同一场景的图片打包成一个纹理图集,或者将一系列的动画序列帧打包成一个纹理图集。 [图片] [图片] 3)、小游戏资源的版本管理 小游戏本身对游戏的包体体积是有大小限制的,势必我们不能将所有游戏图集资源放在游戏的包体内一块打包上传,通常的做法是将非首屏的所有游戏资源放置在一个文件内,然后将该文件夹放在CDN的服务器上,在游戏的开始场景中,通过loading场景去加载CDN上的资源。 由于部门所使用的CDN系统是需要人为手动线上点击发布,所以当新版本的小游戏在本地资源修改后,小游戏新版本发布之后的更新时间和新的资源文件同步时间无法做到绝对的一致,这样就会存在问题。新的资源更新上线了,但是用户的微信小游戏还没更新,那么引用到的资源就会错乱,或者用户的微信小游戏更新了,但是资源还没更新,同样也会产生资源引用错误。所以解决的办法是: 1、每次版本更新之前,对所有资源生成一个文件MD5值的列表保存在一个配置文件内 2、修改资源文件的名称为文件名加文件MD5值的方式 3、将修改好的资源文件增量发布到线上 4、将配置文件放置在本地,通过配置文件来加载远程资源 优化1)首屏资源加载优化 在部分的安卓低端机型下发现,小游戏长时间处在loading,小游戏只有真正绘制了首帧之后,才会隐藏 loading 页。为了减少用户看到loading或黑屏的概率,我们把小游戏首屏的资源放在包体内一块打包上传,这样小游戏下载完成后,首屏也会很快的出现。 [图片] 2)使用对象池 红警小游戏的玩法是有大波的敌人来进攻基地,当敌人被消灭后,我们并不是直接的销毁它,而是将原来的这个游戏的角色Obejct放置在一个对象池内,当下次再创建这个游戏角色时,就可以直接在对象池获取即可,这样可以省去游戏中频繁创建游戏角色对象带来的性能开销。 [图片] 后端接口开发开发koa+myql由于之前用过express但对koa不熟,所以稍微花了一点时间去上手,但实际上koa的上手十分简单,框架使用简洁,基本是开箱即用。 封装async sql方法node8提供了对async原生的支持,稍微封装了sql函数,便可以以同步的写法来执行sql。 [代码]const mysql = require([代码][代码]'mysql'[代码][代码])[代码][代码]const config = require([代码][代码]'./config'[代码][代码])[代码][代码]const pool = mysql.createPool(config.mysql)[代码][代码]// sql辅助函数[代码][代码]const sql = (query, params = []) => [代码][代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]pool.query(query, params, (err, results, fields) => {[代码][代码] [代码][代码]if[代码] [代码](err) [代码][代码]return[代码] [代码]reject(err)[代码][代码] [代码][代码]resolve(results)[代码][代码] [代码][代码]})[代码][代码]})[代码][代码]process.on([代码][代码]'exit'[代码][代码], [代码][代码]function[代码][代码]() {[代码][代码] [代码][代码]console.log([代码][代码]'end'[代码][代码])[代码][代码] [代码][代码]pool.end()[代码][代码]})[代码][代码]// 调用方式写起来跟同步一样舒服[代码][代码]res = await sql([代码][代码]"select * from table where id = ?"[代码][代码], [id])[代码] 接口数据校验为了防止玩家随意篡改分数,跟前端约定一套简单的签名机制,校验失败返回错误 [代码]let jsonStr = JSON.stringify(body)[代码][代码] [代码][代码]let [time, random, sign] = header.sign.split([代码][代码]'|'[代码][代码])[代码][代码] [代码][代码]md5Result = '哈希值'[代码][代码]//只是举例,线上跟这里的例子有出入[代码][代码] [代码][代码]if[代码] [代码](md5Result != sign) {[代码][代码] [代码][代码]ctx.status = 403[代码][代码] [代码][代码]ctx.body = {[代码][代码] [代码][代码]success: [代码][代码]false[代码][代码],[代码][代码] [代码][代码]error: [代码][代码]'invite sign for score'[代码][代码],[代码][代码] [代码][代码]}[代码][代码] [代码][代码]return[代码][代码] [代码][代码]}[代码] 使用JWT登录态维护后端使用了JWT来维护登录态,具体流程为 客户端调用wx.login获取code,并提交到后端接口。 后端请求微信的接口获取用户openid,第一次登录则插入新纪录到mysql,并生成唯一用户id。 把uid,openid等用户标识用JWT签名,发还客户端作为校验凭证。 之后的每次请求都会带上该凭证,在koa的中间件中处理登录态。 登录操作 [代码]//获取openid[代码][代码]const res = await request({[代码][代码] [代码][代码]url: [代码][代码]'https://api.weixin.qq.com/sns/jscode2session'[代码][代码],[代码][代码] [代码][代码]qs: {[代码][代码] [代码][代码]appid: config.appId,[代码][代码] [代码][代码]secret: config.appSecret,[代码][代码] [代码][代码]js_code: body.code,[代码][代码] [代码][代码]grant_type: [代码][代码]'authorization_code'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]json: [代码][代码]true[代码][代码],[代码][代码]})[代码][代码]//...跳过一部分登录、注册逻辑[代码][代码]const exp = Math.floor(Date.now() / 1000) + tokenAge[代码][代码]//生成登录凭证[代码][代码]const token = jwt.sign({ exp, iss: [代码][代码]'redalert-td'[代码][代码], uid: ctx.body.id, openid: ctx.body.openid }, config.secret)[代码].跳过一部分登录、注册逻辑 const exp = Math.floor(Date.now() / 1000) + tokenAge//生成登录凭证const token = jwt.sign({ exp, iss: 'redalert-td', uid: ctx.body.id, openid: ctx.body.openid }, config.secret) 在中间件处理登录态 [代码]const getToken = (ctx) => {[代码][代码]if[代码] [代码](!ctx.header || !ctx.header.authorization) {[代码][代码] [代码][代码]return[代码] [代码]null[代码][代码]}[代码][代码]const authorization = ctx.request.header.authorization[代码][代码]const m = authorization.match(/^Bearer\s(.+)/)[代码][代码]if[代码] [代码](!m) [代码][代码]return[代码] [代码]null[代码][代码] return[代码] [代码]m[1][代码][代码]}[代码][代码]module.exports = async (ctx, next) => {[代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]const token = getToken(ctx)[代码][代码] [代码][代码]if[代码] [代码](!token) [代码][代码]throw[代码] [代码]new[代码] [代码]Error([代码][代码]'need login'[代码][代码])[代码][代码] [代码][代码]const decoded = jwt.verify(token, config.secret)[代码][代码] [代码][代码]if[代码] [代码](decoded.uid !== ctx.request.body.id || !decoded.openid) {[代码][代码] [代码][代码]ctx.status = 403[代码][代码] [代码][代码]ctx.body = { success: [代码][代码]false[代码][代码], error: [代码][代码]'forbidden'[代码] [代码]}[代码][代码] [代码][代码]return[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//省略一部分逻辑[代码][代码]} [代码][代码]catch[代码] [代码](e) {[代码][代码] [代码][代码]log.error(e)[代码][代码] [代码][代码]ctx.status = 401[代码][代码] [代码][代码]ctx.body = { success: [代码][代码]false[代码][代码], error: [代码][代码]'need login'[代码] [代码]}[代码][代码] [代码][代码]return[代码][代码]}[代码][代码]await next()[代码][代码]}[代码] 轮训玩家邀请状态小游戏后期加入了功能:当局邀请多个朋友进入游戏给玩家提供buff,需要邀请后游戏中的玩家能立刻感知邀请成功,当局由于开发时间较为紧迫,前后端联调时间不多,原有的websocket方案改为轮训实现,轮训时间为5s,其中当局的邀请信息不需要入库,存在redis里设置过期时间即可。 服务部署整个小游戏的后端资源分为静态资源和接口两部分,部署在腾讯云上,其中静态资源等存放在腾讯云的cos上,mysql是腾讯云的cdb,接口部分则直接用node8+koa+pm2部署的http服务,并使用了docker部署,镜像使用的是[代码]keymetrics/pm2[代码]。 [代码]FROM keymetrics[代码][代码]/pm2[代码][代码]:8-jessie[代码][代码]WORKDIR [代码][代码]/usr/src/app[代码][代码]COPY package*.json ./[代码][代码]RUN npm [代码][代码]install[代码] [代码]--only=production[代码][代码]COPY . .[代码][代码]EXPOSE 8080[代码][代码]CMD [ [代码][代码]"pm2-runtime"[代码][代码], [代码][代码]"start"[代码][代码], [代码][代码]"process.prod.yml"[代码][代码]][代码]其中https直接使用了腾讯云的负载均衡,非常方便(腾讯云真没给我广告费),不需要在服务器上配置任何https相关服务或在node里实现https,直接上传证书即可,用法和效果都跟stgw差不多。
2019-05-16 - CSS 火焰?不在话下
正文从下面开始。 今天的小技巧是使用纯 CSS 生成火焰,逼真一点的火焰。 嗯,长什么样子?在 CodePen 上输入关键字 [代码]CSS Fire[代码],能找到这样的: [图片] 或者这样的: [图片] 我们希望,仅仅使用 CSS ,效果能再更进一步吗?能不能是这样子: [图片] 如何实现 嗯,我们需要使用 [代码]filter[代码] + [代码]mix-blend-mode[代码] 的组合来完成。 很多 CSS 华而不实的效果都是 [代码]filter[代码] + [代码]mix-blend-mode[代码],很有意思,但是业务中根本用不上,当然多了解了解总没坏处。 如上图,整个蜡烛的骨架, 除去火焰的部分很简单,掠过不讲。主要来看看火焰这一块如何生成,并且如何赋予动画效果。 Step 1: filter blur && filter contrast 模糊滤镜叠加对比度滤镜产生的融合效果。 单独将两个滤镜拿出来,它们的作用分别是: [代码]filter: blur()[代码]: 给图像设置高斯模糊效果。 [代码]filter: contrast()[代码]: 调整图像的对比度。 但是,当他们“合体”的时候,产生了奇妙的融合现象。 先来看一个简单的例子: [图片] 仔细看两圆相交的过程,在边与边接触的时候,会产生一种边界融合的效果,通过对比度滤镜把高斯模糊的模糊边缘给干掉,利用高斯模糊实现融合效果。 利用上述 [代码]filter blur & filter contrast[代码],我们要先生成一个类似火焰形状的三角形。(略去过程) 这里类似火焰形状的三角形的具体实现过程,在这篇文章有详细的讲解:你所不知道的 CSS 滤镜技巧与细节 [图片] 父元素添加 [代码]filter: blur(5px) contrast(20)[代码],会变成这样: [图片] Step 2: 火焰粒子动画 看着已经有点样子了,接下来是火焰动画,我们先去掉父元素的 [代码]filter: blur(5px) contrast(20)[代码] ,然后继续 。 这里也是利用了 [代码]filter[代码] 的融合效果,我们在上述火焰中,利用 SASS 随机均匀分布大量大小不一的圆形棕色 div ,隐匿在火焰三角内部,大概是这样: [图片] 接下来,我们再利用 SASS,给中间每个小圆赋予一个从下往上逐渐消失的动画,并且均匀赋予不同的 [代码]animation-delay[代码],看起来会是这样: [图片] OK,最重要的一步,我们再把父元素的 [代码]filter: blur(5px) contrast(20)[代码] 打开,神奇的火焰效果就出来了: [图片] Step 3: mix-blend-mode 润色 当然,上述效果已经很不错了。经过各种尝试,调整参数,最后我发现加上 [代码]mix-blend-mode: screen[代码] 混合模式,效果更好,得到头图上面的最终效果如下: [图片] 完整源码在我的 CodePen 上:CodePen Demo – CSS Fire 另外一些效果 当然,掌握了这种方法后,这种生成火焰的技巧也可以迁移到其他效果去。下图是我鼓捣到另外一个小 Demo,当 hover 到元素的时候,产生火焰效果: [图片] CodePen Demo – Hover Fire 嗯,这些其实都是对滤镜及混合模式的一些搭配运用。按照惯例,肯定有人会留言喷了,整这些花里胡哨的有什么用,性能又不好,业务中敢上不把你的腿给打骨折。 [图片] 于我而言,虚心接受各种批评质疑及各种不同的观点,当然我是觉得搞技术一方面是实用,另一方面是兴趣使然,自娱自乐。希望喷子绕道~ 回到正题,了解了这种黏糊糊湿答答的技巧后,还可以折腾出其他很多有意思的效果,当然可能需要更多的去尝试,如下面使用一个标签实现的滴水效果: [图片] CodePen Demo – 单标签实现滴水效果 值得注意的细节点 动画虽然美好,但是具体使用的过程中,仍然有一些需要注意的地方: CSS 滤镜可以给同个元素同时定义多个,例如 [代码]filter: blur(5px) contrast(150%) brightness(1.5)[代码] ,但是滤镜的先后顺序不同产生的效果也是不一样的; 也就是说,使用 [代码]filter: blur(5px) contrast(150%) brightness(1.5)[代码] 和 [代码]filter: brightness(1.5) contrast(150%) blur(5px)[代码] 处理同一张图片,得到的效果是不一样的,原因在于滤镜的色值处理算法对图片处理的先后顺序。 滤镜动画需要大量的计算,不断的重绘页面,属于非常消耗性能的动画,使用时要注意使用场景。记得开启硬件加速及合理使用分层技术; [代码]blur()[代码] 混合 [代码]contrast()[代码] 滤镜效果,设置不同的颜色会产生不同的效果,这个颜色叠加的具体算法暂时没有找到很具体的规则细则,使用时比较好的方法是多尝试不同颜色,观察取最好的效果; 细心的读者会发现上述效果都是基于黑色底色进行的,动手尝试将底色改为白色,效果会大打折扣。 最后 本文只是简单的介绍了整个思路过程,许多 CSS 代码细节,调试过程没有展现出来。主要几个 CSS 属性默认大家已经掌握了大概,阅读后可以自行去了解补充更多细节: [代码]filter[代码] [代码]mix-blend-mode[代码] 更多精彩 CSS 技术文章汇总在我的 Github – iCSS ,持续更新,欢迎点个 star 订阅收藏。 好了,本文到此结束,希望对你有帮助 😃 如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。 最后,新开通的公众号求关注,形式希望是更短的篇幅,质量更高一些的技巧类文章,包括但不局限于 CSS: [图片]
2019-04-26 - 小程序怎么打开手电筒
有api可以打开手电筒吗
2019-01-31