前言

笔者之前有一段时间一直在学习Canvas相关的技术知识点,通过参考网上的一些资料文章,学着利用简单的数学和物理知识点实现了一些比较有趣的动画效果,最近刚好翻看到以前的代码,所以这次将这些代码实践重新梳理一遍后整理成文,自己巩固复习的同时,可以和大家一起交流学习。作为【Canvas真好玩】系列的第一篇文章,笔者还是从最经典的黑客帝国开始,在一步一步进行代码具体实践的同时,带领大家进入神奇的Canvas动画的世界。

代码已上传至Github,可以拉下来后直接运行,省掉下面的准备工作环节。

效果图

准备工作

因为之前的代码比较久远,这次打算使用React来重构一遍,还是使用目前使用频率比较高的create-react-app脚手架来搭建项目,在本地找到合适的项目路径,然后执行项目初始化命令:

npm install -g create-react-app
create-react-app react-canvas
复制代码

考虑到后期可能会有一系列的动画效果,所以为了界面美观以及方便管理,这里直接简单使用下React Ant Design来管理动画菜单方便切换到不同的动画,使用react-router-dom来控制路由,同时使用loadable来对路由实现按需加载:

npm install --save antd react-router-dom @loadable/component// 以下依赖遵循antd官网的高级配置,使用babel-plugin-import实现组件代码和样式的按需加载
npm install --save-dev react-app-rewired customize-cra babel-plugin-import
复制代码

安装完成之后修改package.json文件:

/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
-   "eject": "react-scripts eject",
+   "eject": "react-app-rewired eject",
}
复制代码

然后在项目根目录创建一个 config-overrides.js 用于修改默认配置:

+ const { override, fixBabelImports } = require('customize-cra');+ module.exports = override(
+   fixBabelImports('import', {
+     libraryName: 'antd',
+     libraryDirectory: 'es',
+     style: 'css',
+   }),
+ );
复制代码

到目前为止,项目的目录结构如下:

├── node_modules
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   └── serviceWorker.js
├── .gitignore
├── config-overrides.js
├── package.json
├── package-lock.json
└── README.md
复制代码

src目录下有一些在当前项目中不太需要的文件,可以将其删除,然后在src目录下创建router目录用于存放项目路由,views目录用于存放不同路由下的页面,通过antd的Layout组件来实现页面布局,修改后的代码如下:

// src -> router -> index.js
import loadable from '@loadable/component';const routes = [{path: '/hacker',name: '黑客帝国',component: loadable(() => import(/* webpackChunkName: 'hacker' */ '../views/Hacker')),}
];export default routes;
复制代码
// src -> views -> Hacker.js
function Hacker() {const canvasRef = useRef(null);return (<canvas ref={canvasRef} style={{background: '#000'}}/>);
}export default Hacker;
复制代码
// src -> App.js
import React, {useState} from 'react';
import {Redirect, Route, NavLink, Switch, withRouter} from 'react-router-dom';
import {Layout, Menu, Icon} from 'antd';
import routes from './router';
import './App.css';const {Header, Sider, Content} = Layout;function App({location}) {const [collapsed, setCollapsed] = useState(false);const toggle = () => setCollapsed(!collapsed);return (<Layout><Sider trigger={null} collapsible collapsed={collapsed}><div className="title">Canvas真好玩</div><Menu theme="dark" mode="inline"defaultSelectedKeys={[location.pathname.length === 1 ? routes[0].path : location.pathname]}>{routes.map(route =><Menu.Itemkey={route.path}><NavLinkto={route.path}style={{color: 'rgba(255,255,255,.65)'}}activeStyle={{color: '#fff'}}>{route.name}</NavLink></Menu.Item>)}</Menu></Sider><Layout><Header style={{background: '#fff', padding: 0}}><IconclassName="trigger"type={collapsed ? 'menu-unfold' : 'menu-fold'}onClick={toggle}/></Header><Contentstyle={{display: 'flex',justifyContent: 'center',alignItems: 'center',margin: '24px 16px',padding: 24,background: '#fff',minHeight: 280,}}><Switch>{routes.map((route, i) =><Routepath={route.path}exact={route.exact}render={props => <route.component {...props} router={route.routes}/>}key={i}/>)}<Redirect from="/" to="/hacker" exact={true}/></Switch></Content></Layout></Layout>);
}export default withRouter(App);
复制代码
// src -> index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom';
import './index.css';
import App from './App';ReactDOM.render(<Router><App/></Router>,document.getElementById('root'));
复制代码
// src -> App.css
#root {height: 100%;
}.ant-layout {height: 100%;
}.title {padding: 16px 0;text-align: center;color: #fff;font-size: 24px;background-color: rgba(0, 0, 0, .2);
}.trigger {font-size: 18px;line-height: 64px;padding: 0 24px;cursor: pointer;transition: color 0.3s;
}.trigger:hover {color: #1890ff;
}.logo {height: 32px;background: rgba(255, 255, 255, 0.2);margin: 16px;
}
复制代码

至此,我们项目的基本代码结构就已经书写完毕,这里先贴一张我目前已经完成的页面效果:


其实也没有那么好看,主要是为了方便管理菜单,接下来我们就来一步一步分析实现页面中炫酷的黑客帝国效果吧。

实现

在代码实践之前,我们先来分析一下黑客帝国的实现细节,在上面的动画效果中,我们可以知道,动画其实就是由各种英文字母,数字以及特殊符号实现的一个从上到下的距离偏移效果,所以我们在代码中会维护一个集合用于存放所有可能出现的文字。其次,我们可以看出,文字的下坠效果其实是分成了多列的,当然列数会根据Canvas容器的宽度来动态计算。为了实现动画,我们这里可以借助浏览器的requestAnimationFrame来保持每秒60帧的流畅度,相信大部分前端人员对这个Api已经不陌生了,不过这里需要注意以下两点:

  1. 若想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用requestAnimationFrame()
  2. 为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的iframe里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命

通过这个动画Api我们就可以在每帧的时间内清空当前的Canvas容器状态,同时计算每个文字的新坐标并进行绘制,我们可以为每列文字的Y轴偏移定义一个初始变量为1,即表示一个字体单位的大小,每次当文字下落一个字体大小的时候,将这个初始变量加1,这样在下次计算文字坐标的时候,就可以将这个值乘以字体大小从而得出Y轴的坐标,这样在视觉上就达到了一个文字的下坠效果。这里需要提一下的是,Canvas的坐标系统和理科领域的笛卡尔坐标系有点不太一样,采用默认的窗口坐标系统,即原点坐标位于窗口的左上角,沿X轴方向向右为正值,沿Y轴方向向下为正值,在后续计算文字坐标的时候需要注意这里的区别,其实窗口坐标系统中也是有负值的,只是跑到了屏幕之外,我们一般没有注意到而已。
笛卡尔坐标系:


窗口坐标系:

关于Canvas其他的知识点和基础API不是本系列的重点,感兴趣的同学可以自行网上查阅下相关资料,Canvas的绘图API也不是很多,学习门槛不高,很好掌握。基于以上的分析,我们尝试完善一下Hacker.js中的代码:

function Hacker() {const canvasRef = useRef(null);useEffect(() => {// 获取当前的canvas元素const canvas = canvasRef.current;// 获取canvas上下文,2d表示建立一个二维渲染上下文,当然也有基于WebGL的三维渲染上下文,在本系列中暂不考虑const context = canvas.getContext('2d');// 临时保存canvas的宽高信息,问了简便固定800 x 600const w = canvas.width = 800;const h = canvas.height = 600;// 文字颜色const textColor = '#33ff33';// 保存所有可能出现的文字const words = "0123456789qwertyuiopasdfghjklzxcvbnm,./;'[]QWERTYUIOP{}ASDFGHJHJKL:ZXCVBBNM<>?";// 将文字拆分进一个数组const wordsArr = words.split('');// 这里假设每个文字的字体大小为16pxconst font_size = 16;// 根据字体大小动态计算文字列数const columns = w / font_size;// 根据上面的分析,我们创建一个数组保存每列中的文字当前在Y轴上偏移了几个字体单位const dropUnits = [];// 初始化dropUnits,默认值从1开始,而不是0,因为canvas的fillText方法默认是从文字的左下角开始绘制for (let i = 0; i < columns; i++) {dropUnits[i] = 1;}// 设置上下文的填充色和字体大小context.fillStyle = textColor;context.font = `${font_size}px arial`;function draw() {// 核心,// 这里开始循环每一列,// 为每一列创建随机文字,// 同时根据当前列已经下落了几个字体大小来设置文字坐标(坐标原点为canvas容器的左上角)for (let i = 0, len = dropUnits.length; i < len; i++) {const text = wordsArr[Math.floor(Math.random() * wordsArr.length)];const x = i * font_size;const y = dropUnits[i] * font_size;context.fillText(text, x, y);// 当文字已经超出高度边界的时候,需要重置当前列下落的字体单位if (y > h) {dropUnits[i] = 0;}dropUnits[i]++;}}// 循环执行动画(function frame() {// 此处需要再次调用requestAnimationFrame,注意并不是同步递归window.requestAnimationFrame(frame);// 在绘制下一帧的文字之前需要清空当前状态下的所有文字,避免文字被覆盖context.clearRect(0, 0, w, h);draw();}());}, []);return (<canvas ref={canvasRef} style={{background: '#000'}}/>);
}
复制代码

添加以上代码之后,我们来看看目前的效果:

这个效果并不是我们理想中的样子,我们分析一下问题出现的原因,在以上代码实现中,draw函数用于绘制文字,如果检测到文字当前已经超出容器范围,则会重置dropUnits数组中的值为0,那么导致的后果就是,dropUnits数组中的每一项都为0,所以每列文字的Y轴起始坐标始终都是相同的,也就造成上面的效果。所以我们只需要想办法让Y轴的起始坐标错开,那么也就达到了预期的效果了,当然这种错开也是随机的,所以就很容易想到使用Math.random方法增加随机数判断来实现了,我们对以上代码稍作一下修改:

- if (y > h) {
+ if (y > h && Math.random() > 0.98) { // 此处增加随机数判断,只有满足条件后才进行重置dropUnits[i] = 0;
}
复制代码

我简单画了张图来帮助理解一下这个过程,图中两个方块代表两个文字,布尔值代表上面代码中if条件的结果:

上图中可以清楚地看到新增了随机数之后,文字的Y轴坐标产生了差异,修改后的效果如下:

离预期的效果越来越近了,但是这个效果看起来有点生硬,因为我们在每一帧中绘制文字之前,会使用Canvas的clearRect方法将Canvas画布进行清除,所以文字会瞬间出现在下一个坐标点中,形成这种闪烁效果,类似于马路上的红绿灯,在切换颜色之前会将之前的颜色清空,然后瞬间切换。这里我们换一种思路,我们不使用clearRect方法来清除画布,而是在每一帧中使用fillRect方法为画布填充一层淡淡的背景色,以此来实现渐变效果,我们来对代码稍作修改:

// 文字颜色
const textColor = '#33ff33';
+ // 填充背景色
+ const bgColor = 'rgba(0, 0, 0, .1)';- // 设置上下文的填充色和字体大小
- context.fillStyle = textColor;
- context.font = font_size + 'px arial';
function draw() {// 将上述两行代码放到此函数中,因为这里需要重新设置fillStyle+ context.fillStyle = textColor;+ context.font = font_size + 'px arial';
}// 循环执行动画
(function frame() {...- // 在绘制下一帧的文字之前需要清空当前状态下的所有文字,避免文字被覆盖- context.clearRect(0, 0, w, h);+ // 在绘制下一帧的文字之前给画布填充背景色+ context.fillStyle = bgColor;+ context.fillRect(0, 0, w, h);...
}());
复制代码

代码修改完毕后赶紧看下效果吧,应该就和本文开头的效果图一样了,至此,就已经使用Canvas完整地实现了黑客帝国效果,还不错吧。

总结

本文主要是跟大家分享一下使用Canvas来实现炫酷的黑客帝国效果,当然这只是本系列的开篇,后续还会结合简单的数学和物理知识来实现更加有趣的动画效果,希望能和大家一起相互讨论,互相学习。

服务推荐

  • 蜻蜓代理
  • 代理ip
  • 微信域名检测
  • 微信域名拦截检测
  • 微信域名在线拦截检测工具
  • 微信域名在线批量拦截检测工具

【Canvas真好玩】从黑客帝国开始相关推荐

  1. 淘宝的人工封IP技术真好玩

    哈哈,封IP好像也有上下班的,以前早就听说他们人工看程序过滤的日志,没想到真的是这样啊,好像下班时间随便攻击,真好玩! 转载于:https://www.cnblogs.com/caocao/archi ...

  2. c语言logo,真好玩 C语言输出Yahoo动态logo

    真好玩 C语言输出Yahoo动态logo 发布时间:2020-06-21 14:28:27 来源:51CTO 阅读:955 作者:990487026 先上图,看效果: 源代码 chunli@linux ...

  3. dk编程真好玩 python_皮皮学编程(1):从Scratch到Python

    背景 作为一名资深程序员,深深地相信编程不只是为了工作,而是能真正的塑造一个完美的虚拟世界.在虚拟的世界中,你可以发挥你天马行空般的想象力,很少有功能能经常体验到"创造世界"一样的 ...

  4. 技术真好玩第一期(2019-11-01)

    这里是技术真好玩,我们想做一个面向it从业者的周刊,但又不希望局限于IT部分,目标是做完100期,希望能用两年多的时间让自己真正的沉淀下来,也希望能够找到一批志同道合的小伙伴一起搞一些大事情 推荐好文 ...

  5. amd没有relive选项卡_挖掘AMD驱动中的实用功能——Relive真好玩

    原标题:挖掘AMD驱动中的实用功能--Relive真好玩 正所谓所谓早买早享受,晚买没折扣.去年的这个时候二千的588还嫌贵,如今再看到这个价格却恨不得叫老板来一车. 前段时间的车,想都不用想的,晚一 ...

  6. 用easyx画电子钟_Canvas入门-利用Canvas绘制好玩的电子时钟

    在这之前 你需要了解一下方法的使用: beginPath() closePath() moveTo() lineTo() fill() stroke() fillRect() clearRect() ...

  7. Canvas入门-利用Canvas绘制好玩的电子时钟

    在这之前 你需要了解一下方法的使用: beginPath() closePath() moveTo() lineTo() fill() stroke() fillRect() clearRect() ...

  8. python游戏编程入门txt-Python真好玩:教孩子学编程 PDF 完整原版

    前言 第1章 结交一个新朋友,它的名字叫Python 1.1 请叫我Python大人 1 1.2 邀请Python来我的电脑做客 2 1.3 用Python指挥你的电脑 16 1.4 Python的第 ...

  9. 一个有趣的Go项目,3D界面管理k8s集群,真好玩!

    大家好,我是小碗汤,今天分享一个用Golang开发,很好玩的工具`KubeCraftAdmin`[1]:用Minecraft方式管理k8s的工具,感兴趣的兄弟不妨玩一玩.文末有视频,供您鉴赏~ Min ...

最新文章

  1. java对托盘加监听右击报错_java实现系统托盘示例
  2. java程序重新执行一遍_我怎么在jsp里只执行其中一小段java代码,而不把整个页面都重新加载一遍?...
  3. io密集型和cpu密集型java,如何设计CPU密集型与I/O密集型程序
  4. ES6基础-字符串的新特性
  5. PHP 从数组对象中取出数组提示:Undefined property: stdClass::$subject
  6. 网络模型和TCP协议族
  7. nvcc fatal : Unsupported gpu architecture 'compute_11'
  8. mybatis是否接受运算符参数化
  9. jq中ajax请求跨域,用JQuery实现简单的Ajax跨域请求
  10. Android 系统性能优化(12)---MTK 平台UX性能分析方法
  11. Android 浏览器启动应用程序
  12. 基于ZigBee的城市照明监控系统网关节点的软硬件设计
  13. hdu 5145 NPY and girls 莫队
  14. #2 – Rendering Tiers(WPF渲染级别)
  15. 三国杀 官方 游戏规则
  16. card样式 layui_layui后台模板
  17. 计算机无法完成更新如何处理,Win10更新过程中碰到“无法完成更新”怎么办
  18. 玩机搞机----mtk芯片机型 另类制作备份线刷包的方式 读写分区等等
  19. java的 finalize() 方法
  20. 【好书推荐】第一本无人驾驶技术书

热门文章

  1. 刷题记录:牛客NC14975方块与收纳盒
  2. 如果远程计算机运行的是早于,在远程桌面会话,当您连接到远程计算机是运行 Windows Vista SP2 或 Windows Server 2008 SP2 中不正确显示的可视元素...
  3. 『原创』教你如何使用Sandcastle Help File Builder建立MSDN风格的代码文档
  4. 【matlab教程】12、已知函数表达式画函数图
  5. c# sigmoid_[源码和文档分享]基于C#实现的支持AI人机博弈的国际象棋游戏程序
  6. 有心人天不负,互联网寒冬下斩获40W年薪offer,面经分享(附复习脑图)
  7. android5.1本机号码,Android获取手机本机号码的实现方法
  8. python getattr函数_Python getattr()方法
  9. 计算机毕业设计ssm民族地区文化调研与数字化保护技术研究---青海平弦乐库的建设及播放平台开发l3479系统+程
  10. WebStorm的安装使用简单版本教程——WebStorm激活码到设置步骤解