最新要闻
- 环球关注:鲁大师1月新机性能/流畅榜发布:一加11无悬念夺冠
- 全球资讯:剧毒可致死!澳大利亚失踪放射性胶囊被寻回:“大海捞针”式搜寻
- 【报资讯】女子宝娟嗓20年疑似癔症:16岁突然变哑、嗓子却没问题
- 《流浪地球3》粮食稳了?中科院合成淀粉蛋白新突破
- 环球今热点:动物园猴子打架输了逃到狮子园:结果好险
- 快报:市监局回应12.4万保时捷遭抢购后下架:规模较大 正加紧调查
- 每日快讯!下饭神器:乌江榨菜30袋19.9元大促
- 快消息!魅族20就快来了!已有魅友抢先体验:称“感觉不错”
- 焦点快看:故障率惊人!三星昔日旗舰SSD 980 Pro严重翻车 用户投诉多到爆官方出手修复
- 世界快播:雷军对日本任天堂公司欣赏至极:推崇其为“世界的主宰”
- “造假”作弊频发 ChatGPT官方鉴别器紧急发布!不想却遭遇群嘲
- 15万!特斯拉新车Model Q到底有吗:网友喊话如果这外形必冲?
- 世界热点!终于告别祖传67W!小米13 Ultra升级90W快充
- 环球热资讯!咋想的 雪佛兰SUV加油站里试图插队:男女肉身阻挡他车通行
- 每日看点!三年亏损170亿 昔日新能源车一哥“北汽蓝谷”跌下神坛
- “靠服装就能抗菌抗病毒” 真有效还是智商税?
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
记录--原生 canvas 如何实现大屏?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
可视化大屏该如何做?有可能一天完成吗?废话不多说,直接看效果,线上 Demo 地址 lxfu1.github.io/large-scree…。
看完这篇文章(这个项目),你将收获:
- 全局状态真的很简单,你只需 5 分钟就能上手
- 如何缓存函数,当入参不变时,直接使用缓存值
- 千万节点的图如何分片渲染,不卡顿页面操作
- 项目单测该如何写?
- 如何用 canvas 绘制各种图表,如何实现 canvas 动画
- 如何自动化部署自己的大屏网站
效果
实现
项目基于 Create React App --template typescript
搭建,包管理工具使用的 pnpm ,pnpm 的优势这里不多介绍(快+节省磁盘空间),之前在其它平台写过相关文章,后续可能会搬过来。由于项目 package.json 里面有限制包版本(最新版本的 G6 会导致 OOM,官方短时间能应该会修复),如果使用的 yarn 或 npm 的话,改为对应的 resolutions 即可。
(资料图片)
"pnpm": { "overrides": { "@antv/g6": "4.7.10" } }
"resolutions": { "@antv/g6": "4.7.10"},
启动
- clone项目
git clone https://github.com/lxfu1/large-screen-visualization.git
- pnpm 安装
npm install -g pnpm
- 启动:
pnpm start
即可,建议配置 alias ,可以简化各种命令的简写 eg:p start
,不出意外的话,你可以通过 http://localhost:3000/ 访问了 - 测试:
p test
- 构建:
p build
强烈建议大家先 clone 项目!
分析
全局状态
全局状态用的 valtio ,位于项目 src/models
目录下,强烈推荐。
优点:数据与视图分离的心智模型,不再需要在 React 组件或 hooks 里用 useState 和 useReducer 定义数据,或者在 useEffect 里发送初始化请求,或者考虑用 context 还是 props 传递数据。
缺点:兼容性,基于 proxy 开发,对低版本浏览器不友好,当然,大屏应该也不会考虑 IE 这类浏览器。
import { proxy } from "valtio";import { NodeConfig } from "@ant-design/graphs";type IState = { sliderWidth: number; sliderHeight: number; selected: NodeConfig | null;};export const state: IState = proxy({ sliderWidth: 0, sliderHeight: 0, selected: null,});
状态更新:
import { state } from "src/models";state.selected = e.item?.getModel() as NodeConfig;
状态消费:
import { useSnapshot } from "valtio";import { state } from "src/models";export const BarComponent = () => { const snap = useSnapshot(state); console.log(snap.selected)}
当我们选中图谱节点的时候,由于 BarComponent 组件监听了 selected 状态,所以该组件会进行更新。有没有感觉非常简单?一些高级用法建议大家去官网查看,不再展开。
函数缓存
为什么需要函数缓存?当然,在这个项目中函数缓存比较鸡肋,为了用而用,试想,如果有一个函数计算量非常大,组件内又有多个 state 频繁更新,怎么确保函数不被重复调用呢?可能大家会想到 useMemo``useCallback
等手段,这里要介绍的是 React 官方的 cache 方法,已经在 React 内部使用,但未暴露。实现上借鉴(抄袭)ReactCache,通过缓存的函数 fn 及其参数列表来构建一个 cacheNode 链表,然后基于链表最后一项的状态来作为函数 fn 与该组参数的计算缓存结果。
代码位于 src/utils/cache
interface CacheNode { /** * 节点状态 * - 0:未执行 * - 1:已执行 * - 2:出错 */ s: 0 | 1 | 2; // 缓存值 v: unknown; // 特殊类型(object,fn),使用 weakMap 存储,避免内存泄露 o: WeakMap| null; // 基本类型 p: Map | null;}const cacheContainer = new WeakMap ();export const cache = (fn: Function): Function => { const UNTERMINATED = 0; const TERMINATED = 1; const ERRORED = 2; const createCacheNode = (): CacheNode => { return { s: UNTERMINATED, v: undefined, o: null, p: null, }; }; return function () { let cacheNode = cacheContainer.get(fn); if (!cacheNode) { cacheNode = createCacheNode(); cacheContainer.set(fn, cacheNode); } for (let i = 0; i < arguments.length; i++) { const arg = arguments[i]; // 使用 weakMap 存储,避免内存泄露 if ( typeof arg === "function" || (typeof arg === "object" && arg !== null) ) { let objectCache: CacheNode["o"] = cacheNode.o; if (objectCache === null) { objectCache = cacheNode.o = new WeakMap(); } let objectNode = objectCache.get(arg); if (objectNode === undefined) { cacheNode = createCacheNode(); objectCache.set(arg, cacheNode); } else { cacheNode = objectNode; } } else { let primitiveCache: CacheNode["p"] = cacheNode.p; if (primitiveCache === null) { primitiveCache = cacheNode.p = new Map(); } let primitiveNode = primitiveCache.get(arg); if (primitiveNode === undefined) { cacheNode = createCacheNode(); primitiveCache.set(arg, cacheNode); } else { cacheNode = primitiveNode; } } } if (cacheNode.s === TERMINATED) return cacheNode.v; if (cacheNode.s === ERRORED) { throw cacheNode.v; } try { const res = fn.apply(null, arguments as any); cacheNode.v = res; cacheNode.s = TERMINATED; return res; } catch (err) { cacheNode.v = err; cacheNode.s = ERRORED; throw err; } };};
如何验证呢?我们可以简单看下单测,位于src/__tests__/utils/cache.test.ts
:
import { cache } from "src/utils";describe("cache", () => { const primitivefn = jest.fn((a, b, c) => { return a + b + c; }); it("primitive", () => { const cacheFn = cache(primitivefn); const res1 = cacheFn(1, 2, 3); const res2 = cacheFn(1, 2, 3); expect(res1).toBe(res2); expect(primitivefn).toBeCalledTimes(1); });});
可以看出,即使我们调用了 2 次 cacheFn,由于入参不变,fn 只被执行了一次,第二次直接返回了第一次的结果。
项目里面在做 circle 动画的时候使用了,因为该动画是绕圆周无限循环的,当循环过一周之后,后的动画和之前的完全一致,没必要再次计算对应的 circle 坐标,所以我们使用了 cache ,位于src/components/background/index.tsx。
const cacheGetPoint = cache(getPoint); let p = 0; const animate = () => { if (p >= 1) p = 0; const { x, y } = cacheGetPoint(p); ctx.clearRect(0, 0, 2 * clearR, 2 * clearR); createCircle(aCtx, x, y, circleR, "#fff", 6); p += 0.001; requestAnimationFrame(animate); }; animate();
分片渲染
你有审查元素吗?项目背景图是通过 canvas 绘制的,并不是背景图片!通过 canvas 绘制如此多的小圆点,会不会阻碍页面操作呢?当数据量足够大的时候,是会阻碍的,大家可以把 NodeMargin 设置为 0.1 ,同时把 schduler 调用去掉,直接改为同步绘制。当节点数量在 500 W 的时候,如果没有开启切片,页面白屏时间在 MacBookPro M1 上白屏时间大概是 8.5 S;开启分片渲染时页面不会出现白屏,而是从左到右逐步绘制背景图,每个任务的执行时间在 16S 左右波动。
const schduler = (tasks: Function[]) => { const DEFAULT_RUNTIME = 16; const { port1, port2 } = new MessageChannel(); let isAbort = false; const promise: Promise= new Promise((resolve, reject) => { const runner = () => { const preTime = performance.now(); if (isAbort) { return reject(); } do { if (tasks.length === 0) { return resolve([]); } const task = tasks.shift(); task?.(); } while (performance.now() - preTime < DEFAULT_RUNTIME); port2.postMessage(""); }; port1.onmessage = () => { runner(); }; }); // @ts-ignore promise.abort = () => { isAbort = true; }; port2.postMessage(""); return promise; };
分片渲染可以不阻碍用户操作,但延迟了任务的整体时长,是否开启还是取决于数据量。如果每个分片实际执行时间大于 16ms 也会造成阻塞,并且会堆积,并且任务执行的时候没有等,最终渲染状态和预期不一致,所以 task 的拆分也很重要。
单测
这里不想多说,大家可以运行 pnpm test
看看效果,环境已经搭建好;由于项目里面用到了 canvas 所以需要 mock 一些环境,这里的 mock 可以理解为“我们前端代码跑在浏览器里运行,依赖了浏览器环境以及对应的 API,但由于单测没有跑在浏览器里面,所以需要 mock 浏览器环境”,例如项目里面设置的 jsdom、jest-canvas-mock 以及 worker 等,更多推荐直接访问 jest 官网。
// jest-dom adds custom jest matchers for asserting on DOM nodes.import "@testing-library/jest-dom";Object.defineProperty(URL, "createObjectURL", { writable: true, value: jest.fn(),});class Worker { onmessage: () => void; url: string; constructor(stringUrl) { this.url = stringUrl; this.onmessage = () => {}; } postMessage() { this.onmessage(); } terminate() {} onmessageerror() {} addEventListener() {} removeEventListener() {} dispatchEvent(): boolean { return true; } onerror() {}}window.Worker = Worker;
自动化部署
开发过项目的同学都知道,前端编写的代码最终是要进行部署的,目前比较流行的是前后端分离,前端独立部署,通过 proxy 的方式请求后端服务;或者是将前端构建产物推到后端服务上,和后端一起部署。如何做自动化部署呢,对于一些不依赖后端的项目来说,我们可以借助 github 提供的 gh-pages 服务来做自动化部署,CI、CD 仅需配置对应的 actions 即可,在仓库 settings/pages 下面选择对应分支即可完成部署。
例如项目里面的.github/workflows/gh-pages.yml
,表示当 master 分支有代码提交时,会执行对应的 jobs,并借助 peaceiris/actions-gh-pages@v3
将构建产物同步到 gh-pages 分支。
name: github pageson: push: branches: - master # default branch env: CI: false PUBLIC_URL: "/large-screen-visualization"jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: yarn - run: yarn build - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build
本文转载于:
https://juejin.cn/post/7165564571128692773
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
记录--原生 canvas 如何实现大屏?
环球关注:鲁大师1月新机性能/流畅榜发布:一加11无悬念夺冠
全球资讯:剧毒可致死!澳大利亚失踪放射性胶囊被寻回:“大海捞针”式搜寻
【报资讯】女子宝娟嗓20年疑似癔症:16岁突然变哑、嗓子却没问题
《流浪地球3》粮食稳了?中科院合成淀粉蛋白新突破
环球今热点:动物园猴子打架输了逃到狮子园:结果好险
Java多线程:Future和FutureTask
【全球时快讯】MySQL-JDBC反序列化分析
快报:市监局回应12.4万保时捷遭抢购后下架:规模较大 正加紧调查
每日快讯!下饭神器:乌江榨菜30袋19.9元大促
快消息!魅族20就快来了!已有魅友抢先体验:称“感觉不错”
焦点快看:故障率惊人!三星昔日旗舰SSD 980 Pro严重翻车 用户投诉多到爆官方出手修复
世界快播:雷军对日本任天堂公司欣赏至极:推崇其为“世界的主宰”
全球新动态:如何将使用中的域名平滑迁移到京东云?(以原域名注册、域名解析都在万网为例)
环球简讯:最新最常用的Windows/office激活工具
世界今热点:HTTP学习笔记3-HTTP报文
关注:【深读】网络测控系统时钟同步PTP时间同步服务器
环球快报:Git 客户端基本使用及新手常见问题
“造假”作弊频发 ChatGPT官方鉴别器紧急发布!不想却遭遇群嘲
15万!特斯拉新车Model Q到底有吗:网友喊话如果这外形必冲?
世界热点!终于告别祖传67W!小米13 Ultra升级90W快充
环球热资讯!咋想的 雪佛兰SUV加油站里试图插队:男女肉身阻挡他车通行
每日看点!三年亏损170亿 昔日新能源车一哥“北汽蓝谷”跌下神坛
分享会上狂吹MySQL的4大索引结构,没想到大家的鉴赏能力如此的~~~~
“靠服装就能抗菌抗病毒” 真有效还是智商税?
每日快报!大爷撞上奥迪老伴下车后秒晕倒 指责对方为啥停路边:交警到场结局舒适
环球观察:别想歪了!最新研究称西地那非“伟哥”真的能延长男性寿命
【算法训练营day35】LeetCode860. 柠檬水找零 LeetCode406. 根据身高重建队列 LeetCode452. 用最少数量的箭引爆气球
MAUI新生6.4-集合内容类控件难点:CollectionView
今日最新!5万年一遇!绿色“天外来客”正靠近地球:有望肉眼观测
网易发布暴雪游戏退款说明:退款申请截止到6月30日
这才是过年聚会的家 游戏区、棋牌区、动画片区 网友:太和谐了
世界微头条丨三大运营商合力:我国有11亿5G用户!4G用户瑟瑟发抖 求别降速
全球最资讯丨大型国有银行采购摩尔线程国产显卡!造型首次公开
【世界新要闻】try catch finally,try里有return,finally还执行么?
基于k8s的zookeeper搭建
读Java8函数式编程笔记07_设计和架构的原则
20万能买到?极氪003最新预告:3米级加速秒杀百万超跑
滚导和盘托出未来规划 超人新片定档2025年
全球观察:12.4万买下保时捷帕纳梅拉 涉事博主:无成本新车营销成功案例
3层果肉 层层爆浆 榴莲西施榴莲千层蛋糕6寸:两盒69.9元
环球最资讯丨博主怒斥极氪汽车站不起来 居然致敬燃油车奥迪Q3
三星s3370手机有触控笔吗?三星s3370手机参数
怎么删除地址栏里的网址记录?如何恢复删除的网址记录?
tdscdma手机怎么样?tdscdma手机铃声静音怎么解除?
魅族m8什么时候上市的?魅族m8手机参数
All Share Play是什么意思?All Share Play功能是什么?
一劳永逸打一地名是什么?一劳永逸和一蹴而就的区别是什么?
怒晴湘西讲的是什么故事?怒晴湘西大结局是什么?
阳光大道是什么意思?阳光大道打一个生肖是什么?
世界聚焦:linux服务器运行java项目, 监控查看内存、储存空间和cpu占用率
热点!火山引擎 DataTester:0 代码也能实施 A/B 测试的实验平台
世界滚动:一看就懂!任务提交的资源判断在Taier中的实践
全球热文:全球首个面向遥感任务设计的亿级视觉Transformer大模型
萝卜喝醉了会变成什么?脑筋急转弯大全
南国新川是什么意思?南国新川在哪里?
可测水温、室温、体温 一机多用:可孚红外电子体温枪29.9元发车
全球热点评!投入一亿建立优化实验室:一加Ace 2《原神》表现稳了
李一男造车“破梦重圆”?自游家NV现身奇瑞商用车总部
天天动态:男孩撸猫后发烧12天:腋下长鸡蛋大肿块
环球热推荐:知名车评人侮辱特斯拉被判赔10万 车评人上诉
activiti审批流源码,落地版教程
天天新消息丨easy excel 导入导出
全球最资讯丨@vue/cli 插件开发之自动根据目录列表生成别名配置
国内首家!奇瑞霸气官宣:旗下四大品牌全系车型终身质保
环球观察:30年前拍不成 现在香爆!郭帆拍《流浪地球3》 图恒宇写“4”原因揭晓
天天要闻:颜值碾压BBA 马自达旗舰CX-90全球首发:国产后或砍3.3T
支付巨头PayPal“挥刀”裁员7%:2000名员工将被辞退
当前快报:反向操作?特斯拉降价后 宝马、牧马人等油车主动涨价
全球看点:OKR之剑·实战篇04:OKR执行过程优化的那些关键事
用户不升Win11原因扎心!微软正式停售Win10:ISO等继续下载
比SSD便宜 还写不死!净亏4.4亿美元 西数力挺机械硬盘:9亿美元要收铠侠
小米商城惊现神价格!将近500块的手机壳只要10元 米粉疯狂下单
今日报丨曝苹果2025年推出折叠屏MacBook:20.5英寸屏 颠覆以往
环球最资讯丨韩国刷新世界最低生育率纪录 一小学上演1人毕业季:网友唏嘘
联想小新Pro 16 23款轻薄本真机首曝:115W性能释放堪比游戏本
【计算机网络】Stanford CS144 Lab0 : networking warmup 学习记录
天天最资讯丨React框架运行机制
netcore之异步并不是多线程!
天天观点:NVIDIA御用游戏《赛博朋克2077》终于支持DLSS 3:性能暴涨3.9倍!
天天热推荐:机械硬盘:彻底陨落
天天热头条丨地球最黑暗的时刻:被狂轰滥炸长达2000万年
信息:游客岳飞观高喊“还我河山” 砸打秦桧像:景区已报警
焦点热文:丰田章男:一年卖出1049万辆汽车!却败给了电动车
环球快报:特斯拉2022年在华营收181.45亿美元:占比降至约22%
天天微动态丨千呼万唤始出来 《赛博朋克2077》正式支持DLSS3
神十五乘组太空过春节!这一看就是咱中国的空间站:红红火火
天天热头条丨MySQL之MVCC总结理解
环球关注:autojs实例02-为朋友圈指定好友点赞
Prometheus&Grafana基本使用
直播:5万年一遇绿色彗星逼近地球 肉眼可见
python教程:shutil高级文件操作
当前播报:自定义ConditionalOnXX注解(二)
世界焦点!河南矿山开工招聘电话被打爆:因6100万奖金火出圈
天天热议:对不起!《狂飙》反派集体直播“道歉”:阵势让网友欢呼太上头
观热点:日本厂商2023年推出新款磁带收音机:支持U盘数据转录
天天日报丨戴尔灵越Pro 2023系列上架:13代酷睿P系 5999元起
DDS结构的FPGA实现
【算法训练营day34】LeetCode1005. K次取反后最大化的数组和 LeetCode134. 加油站 LeetCode135. 分发糖果
天天观焦点:女子有洁癖:每天消杀双手10次 结果患乏脂性皮炎