如何優雅地輸入文字——給Sketch做一個仿終端輸入窗口
來自專欄界面設計軟體優雅使用指南
前言
在Sketch中輸入文字可以說是設計師及其常見的操作,但是如果就這麼老老實實地打字實在是沒有逼格。如何改變這一局面令我苦思冥想。轉頭看了看旁邊的開發小哥,發現滿屏黑乎乎的命令行界面總能給我們外行一種神秘而優雅的感覺。我想如果給Sketch做一個用命令行樣子的東西來輸入文字,那在別人面前打個字必定不同凡響。
先看效果
https://www.zhihu.com/video/1027610318716829696
這是一個模擬了終端樣式的文字輸入框,並用了vim的操作方式,讓你用起來更加優zhuang雅bi。選中一個文字元素後通過這個輸入框即可優雅地輸入文字。
想直接用現成的,可在此下載插件直接安裝。
在此下載插件
整體結構

- 選中目標文字元素
- 將文字元素的文字傳給做成終端樣式的webview
- 在webview中編輯文字
- 將該文字元素實時修改為編輯後的文字
具體過程
因為只是個功能簡單的Sketch插件,所以全部結構也比較簡單。
要考慮的主要是三部分內容:
- 寫一個終端樣式的webview
- 選中文字元素後把這個元素及其文字內容告訴webview
- 做webview中編輯文字時實時修改對應Sketch元素的文字。
完整項目代碼可以查看GitHub倉庫。
Github倉庫
1. 做一個終端樣子的webview
我們來看一下,一般的終端是如下樣子。

雖然看起來像那麼回事,但是它一般是用來輸入單行命令的(單行文字),而Sketch中的文字有些是多行的,所以我們不如來做一個模擬的vim。

我們來分析一下如何模擬這個vim。它由兩部分組成,上部分的文字編輯框和下部分顯示編輯模式組成。上部分找個別人寫好的支持行號、vim keymap的文字編輯框,改個樣式就好(這裡我用的codemirror);下部分從文字編輯框獲取編輯模式信息並顯示就好了。
ps. 表示空行的~符號我沒有做,各位有空可以想想怎麼實現(其實是我懶得弄了,哈哈哈)

html、css部分比較簡單,不放在這,有興趣的可以直接去GitHub倉庫查看。
// js in webviewimport * as CodeMirror from "codemirror";import vim from "codemirror/keymap/vim.js";import "codemirror/lib/codemirror.css";import "./style.scss";var textarea = document.getElementById("textarea");var editor = document.getElementById("editor");var footer = document.getElementById("footer");var CM = CodeMirror.fromTextArea(textarea, { autofocus: true, lineNumbers: true, lineWrapping: true, scrollbarStyle: null, keyMap: "vim"});CM.setSize(editor.clientWidth, editor.clientHeight);// 讓輸入框大小適應窗口的變化window.addEventListener("resize", () => { CM.setSize(editor.clientWidth, editor.clientHeight);});// footer上顯示輸入模式CM.on("vim-mode-change", e => { if (e.mode !== "insert") { footer.classList.add("hide"); } else { footer.classList.remove("hide"); }});//...
2. 選中文字元素後傳給webview
簡單來說就是在selectionChanged這個事件發生的時候將選中文字元素的文字傳給webview。
// manifest.json{ //... "commands": [ //Sketch中發生selectionChanged事件時就會運行此處命令 { "script": "selection-changed.js", "handlers": { "actions": { "SelectionChanged": "changeHandler" } }, "identifier": "youya-inputter.selection" } ] //...}
// selection-changed.jsimport sketch from "sketch";import { isWebviewPresent, sendToWebview } from "sketch-module-web-view/remote";import parseText from "./parse-text";const document = require("sketch/dom").getSelectedDocument();export function changeHandler(context) { if (!isWebviewPresent("youya-inputter.inputter")) return; let selection = document.selectedLayers; if (!selection.isEmpty && selection.layers[0].type == "Text") { let value = selection.layers[0].text; sendToWebview( "youya-inputter.inputter", parseText(value, selection.layers[0].id) ); }}
這裡有個坑,Skpm中的這個通過插件執行webview中命令的方法(executeJavaScript和sendToWebview),是通過一個字元串發送要運行的命令(感覺像在console里輸入)。而如果你這一串字元里有換行符,那就相當於你在console里輸入了一半命令就按下了回車,總之就會出錯。所以在這傳多行文字時需要手動把換行符轉換成其他的,webview收到後再轉回來。
//parse-text.jsexport default function(text_from_layer, id) { // 此處需要將換行符轉為<br> 否則無法正常執行executeJavaScript let text = text_from_layer.replace(/[
]/g, "<br>"); return set_cm_value({text:" + text + ",id:" + id + "});}
//js in webviewwindow.set_cm_value = function(value) { // id用於記錄選中的目標文字元素 window.element_id = value.id; // 轉換回換行符 let text = value.text.replace(/<br>/g, "
"); CM.setValue(text);};
3. 實時修改Sketch里文字
要實現實時修改,只需要在webview在文字被編輯後發給Sketch,Sketch收到數據後改變對應元素就好了。為了合理控制發送頻率,可以加入debounce。
// js in webview// 編輯文字時獲取文本內容,消除抖動後傳入sketchCM.on( "change", debounce(e => { console.log(e.getValue()); pluginCall("nativeLog", { text: e.getValue(), id: window.element_id }); }, 800));
// open-inputter.jsimport sketch from "sketch";import BrowserWindow from "sketch-module-web-view";import parseText from "./parse-text";const document = require("sketch/dom").getSelectedDocument();export default function() { let selection = document.selectedLayers; const options = { identifier: "youya-inputter.inputter", width: 400, height: 300, show: false, alwaysOnTop: true, backgroundColor: "#222222" }; const browserWindow = new BrowserWindow(options); browserWindow.once("ready-to-show", () => { if (!selection.isEmpty && selection.layers[0].type == "Text") { let value = selection.layers[0].text; browserWindow.webContents.executeJavaScript( parseText(value, selection.layers[0].id) ); } browserWindow.show(); }); browserWindow.webContents.on("nativeLog", function(value) { let text_layer = document.getLayerWithID(value.id); if (text_layer) text_layer.text = value.text; }); browserWindow.loadURL("index.html");}
總結
到此,這樣一個優雅的輸入框就做好了。等下次上班時,同事過來跟你說,「老哥,幫我改一下設計稿這塊的文案唄」,你就可以調出這個命令行,在一陣噼里啪啦中享受優雅的快感了。


推薦閱讀:
