无所不能,将 Vue 渲染到嵌入式液晶屏
liuian 2025-06-18 22:21 2 浏览
该文章转载自公众号@前端时刻,
https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA
前言
之前看了雪碧大佬的将 React 渲染到嵌入式液晶屏觉得很有意思,React能被渲染到嵌入式液晶屏,那Vue是不是也可以呢?所以本文我们要做的就是:
如标题所示,就是将Vue渲染到嵌入式液晶屏。这里使用的液晶屏是0.96 寸大128x64分辨率的SSD1306。而要将Vue渲染到液晶屏,我们还需要一个桥梁,它必须具备控制液晶屏及运行代码的能力。而树莓派的硬件对接能力和可编程性天然就具备这个条件。最后一个问题来了,我们用什么技术来实现呢?
这里我选择了Node.js。原因:
- Atwood定律:“任何可以使用JavaScript来编写的应用,最终会由JavaScript编写。”
- 驱动硬件我大Node.js 一行npm install 走天下。
这个有趣的实践可拆分为这几个步骤:
- 在Node.js运行Vue
- 树莓派连接屏幕芯片
- Node.js驱动硬件
Talk is cheap,Let's Go!!!
跨端渲染
无论是 基于React的React Native 宣称的「Learn Once, Write Anywhere」,还是基于Vue的Weex宣称的「Write Once, Run Everywhere」口号,本质上强调的都是它们跨端渲染的能力。那什么是跨端渲染呢?
React: ReactNative Taro ...
Vue: Weex UniApp ...
各种五花八门的前端框架纷纷袭来,前端工程师们纷纷抱怨学不动了~
老板们看到纷纷笑嘻嘻, App单,前端分,小程序单,前端吞,PC/H5,前端昏。skr~
这些跨平台框架原理其实都大同小异,选定Vue/React作为DSL,以这个 DSL 框架为标准在各端分别编译,在运行时,各端使用各自的渲染引擎(Render Engines)进行渲染,底层渲染引擎中不必关心上层DSL的语法和更新策略,只需要处理 JS Framework 中统一定义的节点结构和渲染指令。也正是因为这一渲染层的抽象,使得跨平台/框架成为了可能。
Vue和React现在都实现了自定义渲染器,下面我们简单介绍一下:
React Reconciler
React16采用新的Reconciler,内部采用了Fiber的架构。react-reconciler模块正是基于v16的新Reconciler实现,它提供了创建React自定义渲染器的能力.
const Reconciler = require('react-reconciler');
const HostConfig = {
// You'll need to implement some methods here.
// See below for more information and examples.
};
const MyRenderer = Reconciler(HostConfig);
const RendererPublicAPI = {
render(element, container, callback) {
// Call MyRenderer.updateContainer() to schedule changes on the roots.
// See ReactDOM, React Native, or React ART for practical examples.
}
};
module.exports = RendererPublicAPI;
Vue createRenderer
vue3 提供了createRender API,让我们创建自定义渲染器。
createRenderer 函数接受两个泛型参数:HostNode 和 HostElement,对应于宿主环境中的 节点 和 元素 类型。
自定义渲染器可以传入特定于平台的类型,如下所示:
import { createRenderer } from 'vue'
const { render, createApp } = createRenderer<Node, Element>({
patchProp,
...nodeOps
})
在Node.js上运行Vue
SFC To JS
<template>
<text x="0" y="0">Hello Vue</text>
<text x="0" y="20">{{ time }}</text>
<text x="0" y="40">Hi SSD3306</text>
</template>
<script>
import { defineComponent, ref, toRefs, onMounted } from "vue";
import dayjs from "dayjs";
export default defineComponent({
setup() {
const time = ref(dayjs().format("hh:mm:ss"));
onMounted(() => {
setInterval(() => {
time.value = dayjs().format("hh:mm:ss");
}, 800);
});
return {
...toRefs({
time,
}),
};
},
});
</script>
要将Vue渲染到液晶屏,我们首先需要让Vue能运行在Node.js上,但是上面这个SFC是没办法被Node.js识别的,它只是vue的编程规范,是一种方言。所以我们需要做的是先将SFC转为js。这里我使用Rollup打包将SFC转为JS(相关配置这里就不啰嗦了,贴个传送门)。到了这一步,Node.js就能成功运行打包后的js代码了,这还不够,这时候Vue组件的状态更新是没办法同步到Node.js的。
Create Custom Renderer
组件状态更新我们需要通知Node.js 更新并渲染液晶屏内容,我们需要创建自定义的"更新策略"。这里就需要用到了我们前面提到的自定义渲染器:createRenderer API。下面我们简单介绍下我们相关使用:
// index.js
// 自定义渲染器
import { createApp } from "./renderer.js";
// 组件
import App from "./App.vue";
// 容器
function getContainer(){
// ...
}
// 创建渲染器,将组件挂载到容器上
createApp(App).mount(getContainer());
// renderer.js
import { createRenderer } from "vue";
// 定义渲染器,传入自定义nodeOps
const render = createRenderer({
// 创建元素
createElement(type) {},
// 插入元素
insert(el, parent) {},
// props更新
patchProp(el, key, preValue, nextValue) {},
// 设置元素文本
setElementText(node, text) {},
// 以下忽略,有兴趣的童鞋可自行了解
remove(el) {},
createText(type) {},
parentNode(node) {},
nextSibling(nide) {},
});
export function createApp(root) {
return render.createApp(root);
}
vue渲染器默认实现了Web平台DOM编程接口,将Virtual DOM 渲染为真实DOM。但是这个渲染器只能运行在浏览器中,不具备跨平台能力。所以我们必须重写nodeOps相关钩子函数,实现对应宿主环境元素的增删改查操作。接下来我们定义一个适配器,来实现相关逻辑。
Adapter
在实现前,我们先来理一下我们要实现的逻辑:
- 创建元素实例 (create)
- 将元素实例插入容器,由容器进行管理 (insert)
- 状态改变时,通知容器进行更新 (update)
// adapter.js
// 文本元素
export class Text {
constructor(parent) {
// 提供一个父节点用于寻址调用更新 (前面提到状态更新由容器进行)
this.parent = parent;
}
// 元素绘制,这里需要实现文本元素渲染逻辑
draw(text) {
console.log(text);
}
}
// 适配器
export class Adapter {
constructor() {
// 装载容器
this.children = [];
}
// 装载子元素
append(child) {
this.children.push(child);
}
// 元素状态更新
update(node, text) {
// 找到目标渲染进行绘制
const target = this.children.find((child) => child === node);
target.draw(text);
}
clear() {}
}
// 容器 === 适配器实例
export function getContainer() {
return new Adapter();
}
好了,基本的适配器已经完成了,接下来我们来实现渲染器。
Renderer Abstract
import { createRenderer } from "vue";
import { Text } from "./adapter";
let uninitialized = [];
const render = createRenderer({
// 创建元素,实例化Text
createElement(type) {
switch (type) {
case "text":
return new Text();
}
},
// 插入元素,调用适配器方法进行装载统一管理
insert(el, parent) {
if (el instanceof Text) {
el.parent = parent;
parent.append(el);
uninitialized.map(({ node, text }) => el.parent.update(node, text));
}
return el;
},
// props更新
patchProp(el, key, preValue, nextValue) {
el[key] = nextValue;
},
// 文本更新,重新绘制
setElementText(node, text) {
if (node.parent) {
console.log(text);
node.parent.clear(node);
node.parent.update(node, text);
} else {
uninitialized.push({ node, text });
}
},
remove(el) {},
createText(type) {},
parentNode(node) {},
nextSibling(nide) {},
});
export function createApp(root) {
return render.createApp(root);
}
树莓派连接屏幕芯片
SSD1306 OLED
OLED,即有机发光二极管( Organic Light Emitting Diode)。是一种液晶显示屏。而SSD1306就是一种OLED驱动芯片。ssd1306本身支持多种总线驱动方式:6800/8080 并口、SPI及IIC接口方式。这里我们选择IIC接口方式进行通信,理由很简单: 1. 接线简单方便(两根线就可以驱动OLED) 2.轮子好找...缺点就是IIC 传输数据效率太慢了,刷新率只有 10FPS 不到。而SPI刷新率最大能达到 2200FPS。
硬件接线
IIC 仅需要 4 根线就可以,其中 2 根是电源,另外 2 根是 SDA 和 SCL。我们使用 IIC-1 接口。下面是树莓派的 GPIO 引脚图。
注意:请一定以屏幕的实际引脚编号为准。
- 屏幕VCC接树莓派1号引脚。- 3.3v电源
- 屏幕GND接树莓派9号引脚。- 地线
- 屏幕SDA接树莓派3号引脚。- IIC 通信中为数据管脚
- 屏幕SCL接树莓派5号引脚。- IIC 通信中为时钟管脚
树莓派启用I2C
1.安装工具包
sudo apt-get install -y i2c-tools
2.启用I2C
- sudo raspi-config
- 选择 Interfacing Options
- Enable I2C
3.检查设备挂载状态
i2c-tools提供的i2cdetect命令可以查看挂载设备
sudo i2cdetect -y 1
Node.js驱动硬件
Node.js Lib
我们先来看几个Node.js库,看完你会不得不感叹~任何可以使用JavaScript来编写的应用,最....
johnny-five
Johnnt-Five 是一个支持 JavaScript 语言编程的机器人和 IOT 开发平台,基于 Firmata 协议。Firmata 是计算机软件和微控制器之间的一种通信协议。使用它,我们可以很简单的架起树莓派和屏幕芯片之间的桥梁。
raspi-io
Raspi IO是一个为Johnny-Five Node.js机器人平台提供的I/O插件,该插件使Johnny-Five能够控制一个Raspberry Pi上的硬件。
oled-font-5x7
5x7 oled字体库,将字符转为16进制编码,让oled程序能够识别。用于绘制文字。
oled-js
兼容johnny-five的oled支持库 (johnny-five本身并不支持oled),提供了操作oled的API。
驱动程序实现
// oled.js
const five = require("johnny-five");
const Raspi = require("raspi-io").RaspiIO;
const font = require("oled-font-5x7");
const Oled = require("oled-js");
const OPTS = {
width: 128, // 分辨率 0.96寸 ssd1306 128*64
height: 64, // 分辨率
address: 0x3c, // 控制输入地址,ssd1306 默认为0x3c
};
class OledService {
constructor() {
this.oled = null;
}
/**
* 初始化: 创建一个Oled实例
* 创建后,我们就可以通过操作Oled实例来控制屏幕了
*/
init() {
const board = new five.Board({
io: new Raspi(),
});
// 监听程序退出,关闭屏幕
board.on("exit", () => {
this.oled && this.remove();
});
return new Promise((resolve, reject) => {
board.on("ready", () => {
// Raspberry Pi connect SSD 1306
this.oled = new Oled(board, five, OPTS);
// 打开屏幕显示
this.oled.turnOnDisplay();
resolve();
});
});
}
// 绘制文字
drawText({ text, x, y }) {
// 重置光标位置
this.oled.setCursor(+x, +y);
// 绘制文字
this.oled.writeString(font, 2, text, 1, true, 2);
}
clear({ x, y }) {
this.oled.setCursor(+x, +y);
}
// 刷新屏幕
update() {
this.oled.update();
}
remove() {
// 关闭显示
this.oled.turnOffDisplay();
this.oled = null;
}
}
export function oledService() {
return new OledService();
}
接下来,我们就可以在适配器中调用oled程序渲染屏幕了~
// index.js
import { createApp } from "./renderer.js";
import { getContainer } from "./adapter";
import { oledService } from "./oled";
import App from "./App.vue";
const oledIns = oledService();
oledIns.init().then(() => {
createApp(App).mount(getContainer(oledIns));
});
// adapter.js
export class Text {
constructor(parent) {
this.parent = parent;
}
draw(ints, opts) {
ints.drawText(opts);
ints.update();
}
}
export class Adapter {
constructor(oledIns) {
this.children = [];
this.oled = oledIns;
}
append(child) {
this.children.push(child);
}
update(node, text) {
const target = this.children.find((child) => child === node);
target.draw(this.oled, {
text,
x: node.x,
y: node.y,
});
}
clear(opts) {
this.oled.clear(opts);
}
}
export function getContainer(oledIns) {
return new Adapter(oledIns);
}
到这一步,就可以成功点亮屏幕啦,来看看效果~
效果展示
参考
将 React 渲染到嵌入式液晶屏
在树莓派上使用 SSD1306 OLED 屏幕
结语
完整代码已上传到Github,如果你觉得这个实践对你有启发/帮助,点个star吧~
Vue已经成功渲染到嵌入式液晶屏了,那下一步是不是可以考虑接个摇杆写个贪吃蛇游戏了~哈哈哈,这很"Javascript"。
"阅读式"的学习使我犯困,所以我更倾向通过一些有趣的实践吸收知识。如果你和我一样爱折腾,欢迎关注~
相关推荐
- vue是什么东西(vue是干什么的)
-
首先看看官方网站是怎么解释的,如下:Vue(读作/vju/,类似于view)是一个用于构建用户界面的渐进式框架。与其他单体框架不同,Vue从一开始就被设计为可逐步采用的。核心库仅专注于视图层,易于...
- 超赞 vue2/3 可视化打印设计VuePluginPrint
-
今天来给大家推荐一款非常不错的Vue可拖拽打印设计器Hiprint。引入使用//main.js中引入安装import{hiPrintPlugin}from'vue-plugin-...
- 使用VuePress2.X构建个人知识博客,域名部署到GitHub Pages中
-
使用VuePress2.X构建个人知识博客,并且用个人域名部署到GitHubPages中什么是VuePressVuePress是一个以Markdown为中心的静态网站生成器。你可以使用Mar...
- 尤雨溪新品Vite的魔力,详解(尤雨溪新品vite的魔力,详解解析)
-
上篇《尤雨溪是个恶魔,Vite三天10更》谈到了Webpack这一系列打包工具出现的原因。这些工具的出现是为了解决ESModules模块系统本身的环境兼容问题、以及零散的模块文件导致的...
- 什么是VUE?vue有什么作用?(vue的主要用途)
-
什么是vue?Vue.js是一套构建用户界面的渐进式框架,Vue采用自下向上增量开发的设计,其核心库只关注视图层,易于上手,同时vue完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复...
- 10个Vue开发技巧「实践」(vue开发需要掌握哪些知识)
-
作者:WahFung转发链接:https://juejin.im/post/5e8a9b1ae51d45470720bdfa路由参数解耦一般在组件内使用路由参数,大多数人会这样做:exportdef...
- 刚搭好vuecli结果官网建议升级vite,于是就升级下
-
从vue-cli迁移到Vite进行迁移的主要原因是速度。Vite的开发服务器速度很快。因为它使用原生浏览器支持JavaScript模块,所以服务器启动时间是即时的。该方法还意味着无论应用程...
- 无所不能,将 Vue 渲染到嵌入式液晶屏
-
该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言之前看了雪碧大佬的将React渲染到嵌入式液晶屏觉得很有意思,R...
- 【开源】基于 Vue 和 Gin 开发的前后端分离的开源框架
-
项目介绍Gin-vue-admin是一个基于vue和gin开发的全栈前后端分离的开发基础平台,旨在快速搭建中小型项目。拥有jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能...
- Firefox正针对Vue.js进行优化,下版本响应速度将“显著提升”
-
IT之家9月7日消息,据Mozilla官方博客发文显示,Firefox浏览器正针对Vue.js进行优化,此前FireFox浏览器在面向Vue3时遇到了一些性能问题。▲图源...
- vue:生命周期钩子函数及顺序(vue3.0生命周期函数)
-
一、vue的钩子相关顺序Vue实例有一个完整的生命周期,在newVue()后,会初始化数据,如下://初始化的入口,各种初始化工作initMixin(Vue);//数据绑定的核心方法,包括常用...
- 使用 Pinia ORM 管理 Vue 中的状态
-
转载说明:原创不易,未经授权,谢绝任何形式的转载状态管理是构建任何Web应用程序的重要组成部分。虽然Vue提供了管理简单状态的技术,但随着应用程序复杂性的增加,处理状态可能变得更具挑战性。这就是为什么...
- 搭建Trae+Vue3的AI开发环境(vue ide 开发工具)
-
从2024年2025年,不断的有各种AI工具会在自媒体中火起来,号称各种效率王炸,而在AI是否会替代打工人的话题中,程序员又首当其冲。作为一个后端开发,这篇文章基于Trae工具,来创建和运行一个简单的...
- 如何在Vue3中使用Nuxt进行服务端渲染开发?
-
服务端渲染SSR(Server-SideRendering)是一种在服务器端生成网页内容的技术,这种实现方式与传统的客户端渲染CSR(Client-SideRendering)有所不同。在SSR中...
- 一篇文章说清 webpack、vite、vue-cli、create-vue 的区别
-
webpack、vite、vue-cli、create-vue这些都是什么?看着有点晕,不要怕,我们一起来分辨一下。先看这个表格:脚手架vue-clicreate-vue构建项目vite打包代码we...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
python使用fitz模块提取pdf中的图片
-
《人人译客》如何规划你的移动电商网站(2)
-
Jupyterhub安装教程 jupyter怎么安装包
-
- 最近发表
-
- vue是什么东西(vue是干什么的)
- 超赞 vue2/3 可视化打印设计VuePluginPrint
- 使用VuePress2.X构建个人知识博客,域名部署到GitHub Pages中
- 尤雨溪新品Vite的魔力,详解(尤雨溪新品vite的魔力,详解解析)
- 什么是VUE?vue有什么作用?(vue的主要用途)
- 10个Vue开发技巧「实践」(vue开发需要掌握哪些知识)
- 刚搭好vuecli结果官网建议升级vite,于是就升级下
- 无所不能,将 Vue 渲染到嵌入式液晶屏
- 【开源】基于 Vue 和 Gin 开发的前后端分离的开源框架
- Firefox正针对Vue.js进行优化,下版本响应速度将“显著提升”
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- uniapp textarea (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)