百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

无所不能,将 Vue 渲染到嵌入式液晶屏

ztj100 2025-04-30 21:23 24 浏览 0 评论

该文章转载自公众号@前端时刻,
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"。

"阅读式"的学习使我犯困,所以我更倾向通过一些有趣的实践吸收知识。如果你和我一样爱折腾,欢迎关注~

相关推荐

30天学会Python编程:16. Python常用标准库使用教程

16.1collections模块16.1.1高级数据结构16.1.2示例...

强烈推荐!Python 这个宝藏库 re 正则匹配

Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...

Python爬虫中正则表达式的用法,只讲如何应用,不讲原理

Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...

Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)

实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...

python爬虫教程之爬取当当网 Top 500 本五星好评书籍

我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...

深入理解re模块:Python中的正则表达式神器解析

在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...

如何使用正则表达式和 Python 匹配不以模式开头的字符串

需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...

先Mark后用!8分钟读懂 Python 性能优化

从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...

Python“三步”即可爬取,毋庸置疑

声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...

简单学Python——re库(正则表达式)2(split、findall、和sub)

1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...

Lavazza拉瓦萨再度牵手上海大师赛

阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...

ArkUI-X构建Android平台AAR及使用

本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...

Deepseek写歌详细教程(怎样用deepseek写歌功能)

以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...

“AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测

“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...

AI音乐制作神器揭秘!3款工具让你秒变高手

在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...

取消回复欢迎 发表评论: