將 Vue 渲染到嵌入式液晶屏

前言
之前看了雪碧大佬的將 React 渲染到嵌入式液晶屏覺得很有意思,React能被渲染到嵌入式液晶屏,那Vue是不是也可以呢?所以本文我們要做的就是:
如標題所示,就是將Vue渲染到嵌入式液晶屏。這里使用的液晶屏是0.96 寸大128x64分辨率的SSD1306。要將Vue渲染到液晶屏,我們還需要一個橋梁,它必須具備控制液晶屏及運行代碼的能力。而樹莓派的硬件對接能力和可編程性天然就具備這個條件。最后一個問題來了,我們用什么技術(shù)來實現(xiàn)呢?

這里我選擇了 Node.js。原因:
Atwood 定律:“任何可以使用 JavaScript 來編寫的應用,最終會由 JavaScript 編寫。” ? 驅(qū)動硬件我大 Node.js 一行npm install走天下。?

在 Node.js 運行 Vue 樹莓派連接屏幕芯片 Node.js 驅(qū)動硬件
跨端渲染
React: ReactNative Taro ...
Vue: Weex UniApp ...
各種五花八門的前端框架紛紛襲來,前端工程師們紛紛抱怨學不動了~

React Reconciler
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
自定義渲染器可以傳入特定于平臺的類型,如下所示:
import?{?createRenderer?}?from?'vue'
const?{?render,?createApp?}?=?createRenderer({
??patchProp,
??...nodeOps
})

在 Node.js 上運行 Vue
SFC To JS
??"0"?y="0">Hello?Vue
??"0"?y="20">{{?time?}}
??"0"?y="40">Hi?SSD3306
Create Custom Renderer
//?index.js
//?自定義渲染器
import?{?createApp?}?from?"./renderer.js";
//?組件
import?App?from?"./App.vue";
//?容器
function?getContainer()?{
??//?...
}
//?創(chuàng)建渲染器,將組件掛載到容器上
createApp(App).mount(getContainer());//?renderer.js
import?{?createRenderer?}?from?"vue";
//?定義渲染器,傳入自定義nodeOps
const?render?=?createRenderer({
??//?創(chuàng)建元素
??createElement(type)?{},
??//?插入元素
??insert(el,?parent)?{},
??//?props更新
??patchProp(el,?key,?preValue,?nextValue)?{},
??//?設(shè)置元素文本
??setElementText(node,?text)?{},
??//?以下忽略,有興趣的童鞋可自行了解
??remove(el)?{},
??createText(type)?{},
??parentNode(node)?{},
??nextSibling(nide)?{},
});
export?function?createApp(root)?{
??return?render.createApp(root);
}
Adapter
創(chuàng)建元素實例 (create) 將元素實例插入容器,由容器進行管理 (insert) 狀態(tài)改變時,通知容器進行更新 (update)
//?adapter.js
//?文本元素
export?class?Text?{
??constructor(parent)?{
????//?提供一個父節(jié)點用于尋址調(diào)用更新?(前面提到狀態(tài)更新由容器進行)
????this.parent?=?parent;
??}
??//?元素繪制,這里需要實現(xiàn)文本元素渲染邏輯
??draw(text)?{
????console.log(text);
??}
}
//?適配器
export?class?Adapter?{
??constructor()?{
????//?裝載容器
????this.children?=?[];
??}
??//?裝載子元素
??append(child)?{
????this.children.push(child);
??}
??//?元素狀態(tài)更新
??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({
??//?創(chuàng)建元素,實例化Text
??createElement(type)?{
????switch?(type)?{
??????case?"text":
????????return?new?Text();
????}
??},
??//?插入元素,調(diào)用適配器方法進行裝載統(tǒng)一管理
??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
硬件接線

屏幕 VCC 接樹莓派 1 號引腳。- 3.3v 電源 屏幕 GND 接樹莓派 9 號引腳。- 地線 屏幕 SDA 接樹莓派 3 號引腳。- IIC 通信中為數(shù)據(jù)管腳 屏幕 SCL 接樹莓派 5 號引腳。- IIC 通信中為時鐘管腳
樹莓派啟用 I2C
1.安裝工具包
sudo?apt-get?install?-y?i2c-tools
2.啟用 I2C
sudo raspi-config 選擇 Interfacing Options Enable I2C
3.檢查設(shè)備掛載狀態(tài)
sudo?i2cdetect?-y?1
Node.js 驅(qū)動硬件
Node.js Lib

驅(qū)動程序?qū)崿F(xiàn)
//?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;
??}
??/**
???*?初始化:?創(chuàng)建一個Oled實例
???*?創(chuàng)建后,我們就可以通過操作Oled實例來控制屏幕了
???*/
??init()?{
????const?board?=?new?five.Board({
??????io:?new?Raspi(),
????});
????//?監(jiān)聽程序退出,關(guān)閉屏幕
????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()?{
????//?關(guān)閉顯示
????this.oled.turnOffDisplay();
????this.oled?=?null;
??}
}
export?function?oledService()?{
??return?new?OledService();
}
//?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);
}
效果展示

結(jié)語

評論
圖片
表情
