如何使用 JavaScript、HTML 和 CSS 構建 Sketch 插件(第 1 部分)

已發表: 2022-03-10
快速總結 ↬如果你曾經使用過 Sketch,很可能有很多時候你會想,“如果 Sketch 能做到這一點,我就能完成手頭的任務更快、更容易、更好。” 好了,別再煩惱了! 在這篇由兩部分組成的文章中,您將學習如何從頭開始構建您自己的 Sketch 插件——為您提供解決這類問題所需的技能。

本教程適用於了解和使用 Sketch 應用程序並且不怕涉足代碼的人。 要從中獲得最大收益,您至少需要具備一些編寫 JavaScript(以及可選的 HTML/CSS)的基本經驗。

我們將要創建的插件稱為“Mosaic”。 在第一部分,我們將了解構成 Sketch 插件的基本文件; 我們將編寫一些 JavaScript 並在一些 HTML 和 CSS 的幫助下為我們的插件創建一個用戶界面。 下一篇文章將介紹如何將用戶界面連接到核心插件代碼,如何實現插件的主要功能,最後,您還將學習如何優化代碼以及插件的工作方式。

我還將分享插件的代碼(JS、HTML、CSS)和文件,您將能夠檢查和用於學習目的。

什麼是 Sketch 插件,它們是如何工作的?

在 Sketch 中,插件是一種“開箱即用”添加 Sketch 中不存在的特性和功能的方法。 考慮到任何給定程序中幾乎總是會缺少一些功能或集成(特別是考慮到任何個人設計師可能有的大量需求!),人們可以開始想像插件如何特別有用和強大。 Sketch 插件可以做幾乎所有你期望的事情,比如操縱圖層的顏色、形狀、大小、順序、樣式、分組和效果,還可以做一些事情,比如向互聯網資源發出請求,向用戶展示界面,還有更多!

在編程方面,所有 Sketch 插件都是用 JavaScript 代碼編寫的。 嗯,實際上,這並不完全正確。 更準確地說,大多數Sketch 插件都是用 JavaScript 編寫的,因為也可以用 Apple 的一種編程語言 Objective-C 和 Swift 編寫 Sketch 插件,儘管它們需要少量的 JavaScript 知識。

不過不用擔心。 在本文中,我們將重點介紹如何單獨使用 JavaScript、HTML 和 CSS 構建 Sketch 插件。 我們不會討論 HTML、CSS 或 JavaScript 的基礎知識——本文假設至少對這三者有一定的了解和經驗。 MDN 開發者網站提供了一個了解 Web 開發更多信息的好地方。

跳躍後更多! 繼續往下看↓

讓我們開始吧!

首先,我們在做什麼?

在本教程中,我將教你如何構建一個基本的、對初學者友好的插件,該插件將能夠創建、複製和修改圖層,並為用戶提供一個漂亮的用戶界面。 通過這樣做,我的目標是建立一個基礎知識,您可以在此基礎上構建並使用它來創建自己的插件。

我們將要構建的插件稱為Mosaic,它實際上是一個“模式生成器”。 餵牠你的圖層,調整一些設置,它會創建一個圖案:

圖片顯示了 Mosaic 插件的 UI,以及一些示例模式。
Mosaic 的 UI,以及一些用它製作的模式示例。 (大預覽)

如果您想安裝和使用 Mosaic,可以從 GitHub 下載完整的插件。

一點歷史:Mosaic 的靈感很大程度上來自一個名為Twist-and-Fade的老式 Adob​​e Fireworks 插件。 Twist-and-Fade 非常強大,能夠在調整其色調、位置、旋轉、大小和不透明度的同時任意多次復製圖層。 該插件甚至能夠生成動畫 GIF,比如這個,它為盒式磁帶中的兩個旋轉元素創建了幀:

顯示帶有旋轉鼓的音樂盒式磁帶的圖像
動畫盒式磁帶(來源)。 (大預覽)

(如果您有興趣了解它的工作原理,這裡有一段演示 Twist and Fade 的視頻。)

出於本教程的目的,我們將為 Sketch 構建一個有點類似的插件,但有意簡化以使教程盡可能易於訪問。 具體來說,我們的插件將能夠:

  • 複製任何 Sketch 圖層(位圖或矢量)並調整副本圖層的位置、旋轉和不透明度。 這將向我們介紹使用 Sketch 的 JavaScript API 操作圖層。
  • 顯示使用 HTML、CSS 和 JS 創建的用戶界面,它將教您如何使用您可能已經熟悉的 Web 技術輕鬆地為插件創建界面。 插件界面非常重要,因為它是我們收集用戶關於用戶希望生成的馬賽克圖像外觀的輸入的方式。

在十秒內創建我們的基礎插件

首先,我們將為我們要構建的插件創建“基礎”(或模板)。 我們可以手動創建構成插件的所有必要文件和文件夾,但幸運的是我們不必這樣做——因為 Sketch 可以為我們完成。 在我們生成模板插件之後,我們將能夠根據我們認為合適的方式對其進行自定義。

我們可以使用一種非常快速且簡單的技術來創建模板插件,當我需要將插件組合在一起以解決我在特定時刻處理的任何問題時,這幾乎是我的首選方法。 以下是它的工作原理:

打開 Sketch,檢查屏幕頂部的菜單欄,然後單擊Plugins -> Run Script 。 這將打開一個對話框,我們可以使用它來測試和運行代碼。 我們還可以將我們輸入的任何代碼保存為插件,這是我們現在特別感興趣的部分。

清除此對話框中已有的任何代碼,並將其替換為以下演示代碼:

 const UI = require("sketch/ui"); UI.message(" Hey there, you fantastic plugin developer you! This is your plugin! Talking to you from the digital computer screen! In Sketch! Simply stupendous!");

接下來,點擊窗口左下角的Save Script as Plugin ,輸入您希望該插件具有的任何名稱(在我們的例子中,這是“馬賽克”),然後再次Save Script as Plugin

按窗口左下角的“保存”,然後輸入您希望此插件具有的任何名稱。 (大預覽)

信不信由你,我們已經完成了——剩下的就是吃我們剛烤好的蛋糕。 有趣的部分來了。 再次打開插件菜單,您應該會看到如下內容:您的全新插件被列為“Mosaic”! 點擊它!

(大預覽)

恭喜,你剛剛編寫了你的第一個 Sketch 插件!

點擊“Mosaic”後你應該看到的應該和上面的短視頻一樣,屏幕底部會出現一條不顯眼的工具提示消息,以“Hey there...”開頭——這正是我們粘貼的代碼告訴它的內容去做。 這就是它使這項技術如此出色的原因:它可以輕鬆粘貼、修改和測試代碼,而無需從頭開始構建插件。 如果您熟悉或曾經使用過瀏覽器的 Web 控制台,基本上就是這樣。 在構建和測試代碼時,必須擁有這個工具。

讓我們快速了解一下您添加的代碼的作用:

首先導入Sketch內置JS庫的sketch/ui模塊,賦值給UI變量。 該模塊包含幾個有用的與接口相關的方法,我們將使用其中一個:

 const UI = require("sketch/ui");

接下來,它使用我們希望在我們看到的工具提示中顯示的文本字符串調用message方法(它是sketch/ui模塊的一部分):

 UI.message(" Hey there, you fantastic plugin developer you! This is your plugin! Talking to you from the digital computer screen! In Sketch! Simply stupendous!");

message()方法提供了一種向用戶呈現不顯眼的消息的好方法; 它非常適合您不需要竊取焦點(非模態)並且不需要任何花哨的按鈕或文本字段的情況。 還有其他方法可以呈現常見的 UI 元素,例如警報、提示等,其中一些我們將在構建 Mosaic 時使用。

自定義我們插件的元數據

我們現在有一個基本的插件可以開始,但我們仍然需要進一步調整它並使其真正成為我們的。 我們的下一步將是更改插件的元數據。

對於這一步,我們需要查看所謂的插件包。 當您在“運行腳本”窗口中點擊保存時,Sketch 將您的插件保存為名為Mosaic.sketchplugin的文件夾,您可以在~/Library/Application Support/com.bohemiancoding.sketch3/Plugins目錄中找到該文件夾。 記起來有點長而且煩人。 作為快捷方式,您還可以通過Plugins -> Manage Plugins -> (right-click your plugin) -> Reveal Plugins Folder將其拉起。 儘管它在 Finder 中顯示為單個文件,但它實際上是一個文件夾,其中包含我們的插件運行 Sketch 所需的所有內容。 儘管它是一個文件夾,但它顯示為單個文件的原因是,當您第一次安裝 Sketch 時,Sketch 將.sketchplugin擴展名註冊為“捆綁包”(一種顯示為文件的特殊文件夾)並要求它自動打開打開時在草圖中。

讓我們看看裡面。 右鍵單擊Mosaic.sketchplugin ,然後單擊“顯示包內容”。 在內部,您應該看到以下目錄結構:

 Contents/ └ Resources/ └ Sketch/ └ manifest.json └ script.cocoascript

您可能想知道為什麼其中有一個擴展名為.cocoascript的文件。 別擔心——它只是一個普通的 JavaScript 文件,只包含我們之前輸入的代碼。 繼續將此文件重命名為index.js ,這會將目錄結構更改為如下所示:

 Contents/ └ Resources/ └ Sketch/ └ manifest.json └ index.js

在插件包中組織文件的最常見方法如下:您的代碼 (JavaScript) 和manifest.json屬於Sketch/ ,資源(例如圖像、音頻文件、文本文件等)屬於Resources/

讓我們從調整名為manifest.json的文件開始。 在您喜歡的代碼編輯器中打開它,例如 Visual Studio Code 或 Atom。

您會看到目前這裡的內容相對較少,但我們很快會添加更多內容。 插件清單主要有兩個目的:

  1. 首先,它向用戶提供描述插件的元數據——比如它的名稱、版本、作者姓名等。 Sketch 在Sketch -> Preferences -> Plugins對話框中使用此信息來為您的插件創建列表和描述。
  2. 其次,它還告訴 Sketch 如何開始你的業務; 也就是說,它告訴 Sketch 您希望插件菜單的外觀、分配給插件的熱鍵以及插件代碼所在的位置(以便 Sketch 可以運行它)。

考慮到目的 #1,向用戶描述插件,您可能會注意到現在沒有給出描述作者,這會使用戶感到困惑並使插件難以識別。 讓我們通過將相關鍵的值調整為:

 { "description": "Generate awesome designs and repeating patterns from your layers!", "author": "=> Your name here <=" }

接下來,讓我們調整插件的標識符。 此標識符使用所謂的“反向域表示法”,這是一種非常簡潔(或無聊,隨您選擇)的方式,表示“獲取您網站的域,顛倒順序,然後將您的產品名稱放在最後。” 這將出現如下內容: com.your-company-or-your-name-its-not-that-big-a-deal.yourproduct

你不必遵守這個命名約定——你可以在這裡放任何你想要的東西,只要它足夠獨特以避免與其他插件衝突(儘管堅持 RDN 格式可能是個好主意,特別是因為它提供一個簡單的、可重用的插件標識符系統)。

為此,請將您的標識符更改為com.your-name.mosaic

 { "identifier": "com.your-name.mosaic" }

我個人喜歡獲取所有與元數據相關的鍵(標題、作者、標識符等)並將它們分組到清單頂部附近,這樣它們就不會散佈在各處,並在我需要找到它們時幫助我保持理智.

接下來,我們來看看menucommands鍵。 這兩個負責告訴 Sketch 調用什麼代碼以及響應什麼。

如果您查看menu鍵,您會看到它包含一個title鍵,其值是我們的插件將在Plugins菜單中顯示的名稱。 它還有一個items鍵,它是一個命令標識符列表:

 { "menu": { "title": "Mosaic", "items": [ "com.bohemiancoding.sketch.runscriptidentifier" ] } }

現在這個列表中只有一個命令標識符, "com.bohemiancoding.sketch.runscriptidentifier" 。 命令標識符始終指向commands列表中的命令。 現在我們的插件只有一個命令,就是帶有這個標識符的那個:

 { "commands": [ { "script" : "script.cocoascript", "name" : "Mosaic", "handlers" : { "run" : "onRun" }, "identifier" : "com.bohemiancoding.sketch.runscriptidentifier" } ] }

每當您向menu條目添加命令標識符時,Sketch 將查找具有該標識符的命令條目並顯示其name鍵的值(在本例中為“Mosaic”)並將其顯示在插件的菜單中的標識符。

至於命令所扮演的角色,我們可以將命令條目視為一種方式來告訴 Sketch 在調用該命令時我們想要運行插件 JavaScript 代碼中的哪個函數,“調用”通常是用戶點擊相關菜單物品。 命令條目本身不做任何事情,它只是 JSON——它只是向 Sketch 提供了一個描述,說明在調用命令時在哪裡尋找它需要運行的 JavaScript。

到目前為止,我們已經討論了命令的nameidentifier鍵的作用,但是命令中還有另外兩個鍵需要處理: scripthandlers

script鍵告訴 Sketch 它應該運行的 JavaScript 文件在哪裡。 請注意 Sketch 是如何假設有問題的腳本文件位於Sketch/文件夾中的,這就是為什麼為了簡單起見,您需要確保所有 JavaScript 代碼都位於Sketch/文件夾下的某個位置。 在我們從這個鍵開始之前,重要的是你確保你將這個鍵的值更改為index.js ,就像我們之前重命名文件一樣。 否則,Sketch 將無法找到並運行您的 JavaScript 文件。

handlers鍵的值是 Sketch 用來確定 JavaScript 中要調用的函數的值。 在這裡,我們只有一個處理程序集: run ,其值為onRunrun是預定義的內置 Sketch動作的名稱。 當用戶單擊引用此命令的菜單項時,將始終調用此run操作。 onRun是自動生成的script.cocoascript文件(我們將其重命名為index.js )中的函數名稱,以及我們希望在run事件發生時調用的函數,即當用戶單擊菜單項時。

在我們目前的例子中,這個過程是這樣的:

  1. 用戶點擊我們的菜單項。
  2. Sketch 查找與該菜單項關聯的命令。
  3. Sketch 找到命令引用的腳本文件並運行它(在這種情況下,這意味著它執行index.js中的 JavaScript)。
  4. 由於此命令是由單擊菜單項調用的,因此它被視為run操作。 這意味著 Sketch 將查看命令的handlers.run值以供下一個調用的函數,在本例中為onRun
  5. Sketch 調用onRun函數。

命令最常被調用以響應用戶單擊您的菜單項之一,但也可以調用它們以響應其他用戶操作,例如用戶更改選擇或圖層上的屬性。 但是,對於這個插件,我們不會使用任何其他操作。 (您可以在 Action API 幫助頁面中了解有關操作及其工作原理的更多信息。)

在我們從這個清單繼續之前,我們需要做另外兩個調整。 現在,我們的菜單結構如下:

 Mosaic └ Mosaic 
圖像顯示 Mosaic 菜單項冗餘嵌套在另一個名為 Mosaic 的菜單中
相當多餘,對吧? (大預覽)

…這有點多餘,因為我們的插件只有一個菜單項。 它還為我們的用戶增加了一些不必要的摩擦,因為我們的插件現在需要兩次點擊才能調用,而不是一次。 我們可以通過添加isRoot: true到我們的menu來解決這個問題:

 { "menu": { "title" : "Mosaic", "items" : [ "com.bohemiancoding.sketch.runscriptidentifier" ], "isRoot": true } }

這告訴 Sketch 將第一級菜單項直接放在Plugins菜單下,而不是將它們嵌套在菜單的title下。

點擊保存並返回到 Sketch。 您應該看到現在Mosaic -> Mosaic已被Mosaic取代——完美!

顯示 Mosaic 插件 UI 的圖像
馬賽克的用戶界面。 (大預覽)

至於我們的第二個調整,讓我們繼續並將這個命令標識符重命名為不那麼笨重的東西。 由於命令標識符只需要在單個插件的上下文中是唯一的,我們可以安全地將其重命名為更簡潔明了的名稱,例如"open"

 { "commands": [ { ... "identifier" : "open" } ], "menu": { ... "items" : [ "open" ] } }

在我們繼續之前,請注意菜單也可以包含其他菜單。 您可以通過在另一個菜單的items列表中嵌套另一個{ title: ..., items: ... }條目來輕鬆創建子菜單:

 { "menu": { "title" : "Mosaic", "items" : [ "open", { "title" : "I'm a sub-menu!", "items" : [ "another-command-identifier" ] } ] } }

構建插件的用戶界面

到目前為止,我們已經編寫了一些演示代碼並自定義了我們插件的清單。 我們現在將繼續創建它的用戶界面,它本質上是一個嵌入在窗口中的網頁(類似於您熟悉使用的瀏覽器):

插件的窗口。 (大預覽)
顯示組成我們插件界面的組件的圖像:窗口和 Web 視圖
組成我們插件的組件。 (大預覽)

窗戶

Mosaic的用戶界面設計有自己的窗口,我們可以認為是最基本的組件; 我們將從它開始。 為了創建和顯示一個窗口,我們必須使用一個默認內置在 macOS 中的類,稱為NSWindow 。 在本教程的其餘部分,我們實際上會做很多事情(使用內置的 API,如NSWindow ),如果你不熟悉它,這可能看起來有點令人生畏,但別擔心——我會解釋一路上的一切!

注意:當我們談論內置 API 時,我們能夠使用這個類原因是由於 Sketch 插件使用的 JavaScript 運行時中存在的橋接器。 此橋會自動導入這些通常僅適用於本機應用程序的內置類、方法和函數。

在代碼編輯器中打開Sketch/index.js ,刪除已經存在的內容,然後粘貼以下內容:

 function onRun(context){ const window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_( NSMakeRect(0, 0, 145, 500), NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable, NSBackingStoreBuffered, false ); window.releasedWhenClosed = false; window.makeKeyAndOrderFront(nil); };

讓我們看一下第一段代碼的作用:

 function onRun(context){

還記得之前我們談到命令及其功能的時候,我們告訴 Sketch 響應菜單點擊調用onRun嗎? (如果您需要復習,請重新訪問上面的那部分,然後再回來。)這部分所做的就是創建該函數。 您還會注意到我們的onRun函數帶有一個context參數。 這是 Sketch 將調用您的命令處理程序的參數,可以為我們提供某些信息。 稍後,我們將使用它來獲取用戶計算機上插件包的 URL。

 const window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer( NSMakeRect(0, 0, 145, 500), NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable, NSBackingStoreBuffered, false );

在這裡,我們實際上正在做一些事情:

  1. 首先,我們在NSWindow上調用alloc() ; 這基本上意味著“為 NSWindow 的實例留出一些內存”。 知道您必須為要創建的本地類的每個實例執行此操作就足夠了。 alloc方法在每個本機類中都可用。
  2. 接下來,我們調用NSWindow的初始化方法(即實際創建NSWindow實例的方法),該方法名為initWithContentRect:styleMask:backing:defer: 。 你會注意到這與我們在上面的代碼中調用的不同——每個參數之間都有一堆冒號( : 。 由於我們不能在 JavaScript 中使用該語法,Sketch 方便地將其重命名為我們可以實際使用的名稱,方法是用下劃線替換冒號,這就是我們獲得其 JS 名稱的方式: initWithContentRect_styleMask_backing_defer
  3. 接下來,我們傳入該方法需要的每個參數。 對於第一個參數contentRect ,我們提供了一個大小足以容納我們的用戶界面的矩形。
  4. 對於styleMask ,我們使用一個位掩碼,表示我們希望我們的窗口有一個關閉按鈕、一個標題欄,並且可以調整大小。
  5. 接下來的兩個參數backingdefer總是會設置為NSBackingStoreBufferedfalse ,所以我們真的不需要擔心它們。 (此方法的文檔更詳細地說明了為什麼會這樣。)
 window.releasedWhenClosed = false; window.makeKeyAndOrderFront(null);

這裡我們將NSWindowreleasedWhenClosed屬性設置為false ,這意味著:“嘿! 不要僅僅因為用戶關閉它就從內存中刪除這個窗口。” 然後我們調用makeKeyAndOrderFront (null) ,意思是:“把這個窗口移到最前面,並給它鍵盤焦點。”

Web 視圖:界面

為了讓事情變得更簡單,我已經編寫了我們將要使用的插件 Web 用戶界面的 HTML 和 CSS 代碼; 我們需要添加的唯一剩餘代碼將確保我們能夠在它和我們的 Sketch 插件代碼之間進行通信。

接下來,下載 HTML 和 CSS 代碼。 下載後,解壓縮,然後將名為“web-ui”的文件夾移動到我們插件的 Resources 文件夾中。

注意編寫和優化實際的 HTML/CSS 代碼超出了本教程的範圍,因為它的重點是支持插件核心功能的 JavaScript; 但是如果您想了解更多信息,網絡上有大量關於此主題的教程。

如果你現在運行我們的插件,你會看到它顯示了一個窗口——是的,進步! 但它是空的,沒有標題,也不是特別有用。 我們需要讓它顯示我們的網絡界面。 為此,我們需要使用另一個原生類WKWebView ,這是一個專門用於顯示 Web 內容的視圖。

我們將在為窗口編寫的代碼下方添加創建WKWebView所需的代碼:

 function onRun(context){ // Create window const window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer( NSMakeRect(0, 0, 145, 500), NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable, NSBackingStoreBuffered, false ); window.releasedWhenClosed = false; // Create web view, and set it as the view for our window to display const webView = WKWebView.alloc().init(); window.contentView = webView; // Load our UI into the web view const webUIFolderURL = context.scriptURL .URLByDeletingLastPathComponent() .URLByAppendingPathComponent("../Resources/web-ui/"); const indexURL = webUIFolderURL.URLByAppendingPathComponent("index.html"); webView.loadFileURL_allowingReadAccessToURL(indexURL, webUIFolderURL); // Make window key and move to front window.makeKeyAndOrderFront(nil); };

如果我們現在運行我們的插件,我們會看到現在我們打開了一個窗口,顯示我們的 Web 用戶界面。 成功!

同樣,在繼續之前,讓我們檢查一下我們添加的代碼的作用:

 const webView = WKWebView.alloc().init();

這看起來應該很熟悉——它與我們在創建NSWindow時所做的基本相同:為 Web 視圖分配內存,然後對其進行初始化。

 window.contentView = webView;

這行代碼告訴我們的窗口顯示我們剛剛創建的 web 視圖。

 const webUIFolderURL = context.scriptURL .URLByDeletingLastPathComponent() .URLByAppendingPathComponent("../Resources/web-ui/");

這裡我們的目標是創建一個指向我們之前創建的web-ui文件夾的 URL。 為了獲取該 URL,我們需要一些方法來確定我們的插件包在用戶文件系統中的位置。 這裡我們使用context.scriptURL屬性,它為我們提供了當前正在運行的腳本的 URL。 但是,這並沒有像您期望的那樣給我們一個 JavaScript String ,而是一個本機類的實例NSURL ,它上面有一些方法可以使操作 URL 字符串更容易。

我們需要轉換context.scriptURL給我們的東西——

 file://path-to-your-plugin/Contents/Sketch/index.js

- 進入:

 file://path-to-your-plugin/Contents/Resources/web-ui/

一步步:

  1. 第一次調用URLByDeletingLastPathComponent()會給我們file://path-to-your-plugin/Contents/Sketch/
  2. 再次調用URLByDeletingLastPathComponent()會給我們file://path-to-your-plugin/Contents/
  3. 最後,使用URLByAppendingPathComponent ("Resources/web-ui/")Resources/web-ui/添加到末尾會給我們file://path-to-your-plugin/Contents/Resources/web-ui/

我們還需要創建第二個直接指向index.html文件的 URL:

 const indexURL = webUIFolderURL.URLByAppendingPathComponent("index.html");

最後,我們告訴我們的 web 視圖加載index.html並允許它訪問web-ui文件夾的內容:

 webView.loadFileURL_allowingReadAccessToURL(indexURL, webUIFolderURL);

好的。 到目前為止,我們有一個窗口可以顯示我們的 Web 用戶界面,就像我們想要的那樣。 然而,它還沒有完全完成——我們的原始設計沒有標題欄(或“chrome”),但我們當前的窗口有。 還有一個事實是,當我們在 Sketch 文檔中單擊時,該文檔會移動到我們的窗口前面,這不是我們想要的——我們希望用戶能夠與插件窗口Sketch 文檔進行交互,而不必不斷地從一個窗口重新聚焦到另一個窗口。

為了解決這個問題,我們首先需要擺脫默認的窗口鑲邊,只保留按鈕。 添加下面的兩行代碼將擺脫標題欄。

注意:和以前一樣,我們在下面使用的所有屬性和方法都記錄在NSWindow的文檔頁面中。

 window.titlebarAppearsTransparent = true; window.titleVisibility = NSWindowTitleHidden;

接下來的兩行代碼將刪除我們不需要的窗口按鈕(在 MacOS 術語中也稱為“交通燈”)——“縮放”和“最小化”——只留下“關閉”按鈕:

 window.standardWindowButton(NSWindowZoomButton).hidden = true; window.standardWindowButton(NSWindowMiniaturizeButton).hidden = true;

在此過程中,讓我們繼續更改窗口的背景顏色以匹配我們的 Web UI:

 window.backgroundColor = NSColor.colorWithRed_green_blue_alpha(1, 0.98, 0.98, 1);

接下來,我們需要做一些事情來讓我們的浮動插件窗口保持在其他窗口之上,這樣用戶就可以與他們的 Sketch 文檔進行交互,而不必擔心 Mosaic 的窗口消失。 我們可以為此使用一種特殊類型的NSWindow ,稱為NSPanel ,它能夠“保持在”其他窗口的頂部。 只需將NSWindow更改為NSPanel ,這是一個單行代碼更改:

 const window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer(

現在我們告訴我們的面板窗口浮動(保持在所有其他窗口之上),並且僅在必要時獲取鍵盤/鼠標焦點:

 window.floatingPanel = true; window.becomesKeyOnlyIfNeeded = true;

我們還可以調整我們的窗口,以便它自動重新打開它的最後一個位置:

 window.frameAutosaveName = "mosaic-panel-frame";

這條線基本上是說“記住這個窗口的位置,用 Sketch 的偏好將它保存在關鍵的mosaic-panel-frame下”。

總之,我們現在有以下代碼:

 function onRun(context){ // Create window const window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer( NSMakeRect(0, 0, 145, 500), NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable, NSBackingStoreBuffered, false ); window.becomesKeyOnlyIfNeeded = true; window.floatingPanel = true; window.frameAutosaveName = "mosaic-panel-frame"; window.releasedWhenClosed = false; window.standardWindowButton(NSWindowZoomButton).hidden = true; window.standardWindowButton(NSWindowMiniaturizeButton).hidden = true; window.titlebarAppearsTransparent = true; window.titleVisibility = NSWindowTitleHidden; window.backgroundColor = NSColor.colorWithRed_green_blue_alpha(1, 0.98, 0.98, 1); // Create web view, and set it as the view for our window to display const webView = WKWebView.alloc().init(); window.contentView = webView; // Load our UI into the webview const webUIFolderURL = context.scriptURL .URLByDeletingLastPathComponent() .URLByAppendingPathComponent("../Resources/web-ui/"); const indexURL = webUIFolderURL.URLByAppendingPathComponent("index.html"); webView.loadFileURL_allowingReadAccessToURL(indexURL, webUIFolderURL); // Make window key and move to front window.makeKeyAndOrderFront(nil); };

組織代碼

在我們進入下一部分之前,組織我們的代碼是一個好主意,以便更容易導航和調整。 由於我們還有很多代碼要添加,並且我們希望避免index.js成為我們所有代碼的混亂垃圾場,讓我們將它們拆分一下並將我們的 UI 特定代碼移動到一個名為ui.js的文件中,在Sketch文件夾下。 我們還將提取我們所做的一些 UI 任務,例如創建 Web 視圖和窗口,並將其提取到它們自己的函數中。

創建一個名為ui.js的新文件,並在其中插入以下代碼:

 // Private var _window; function createWebView(pageURL){ const webView = WKWebView.alloc().init(); webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent()); return webView; }; function createWindow(){ const window = NSPanel.alloc().initWithContentRect_styleMask_backing_defer( NSMakeRect(0, 0, 420, 646), NSWindowStyleMaskClosable | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable, NSBackingStoreBuffered, false ); window.becomesKeyOnlyIfNeeded = true; window.floatingPanel = true; window.frameAutosaveName = "mosaic-panel-frame"; window.releasedWhenClosed = false; window.standardWindowButton(NSWindowZoomButton).hidden = true; window.standardWindowButton(NSWindowMiniaturizeButton).hidden = true; window.titlebarAppearsTransparent = true; window.titleVisibility = NSWindowTitleHidden; window.backgroundColor = NSColor.colorWithRed_green_blue_alpha(1, 0.98, 0.98, 1); return window; }; function showWindow(window){ window.makeKeyAndOrderFront(nil); }; // Public function loadAndShow(baseURL){ if(_window){ showWindow(_window); return; } const pageURL = baseURL .URLByDeletingLastPathComponent() .URLByAppendingPathComponent("../Resources/web-ui/index.html"); const window = createWindow(); const webView = createWebView(pageURL); window.contentView = webView; _window = window; showWindow(_window); }; function cleanup(){ if(_window){ _window.orderOut(nil); _window = null; } }; // Export module.exports = { loadAndShow, cleanup };

我們在此處進行了一些重要的更改,值得注意。 除了我們已經為創建、隱藏和顯示我們的窗口及其 Web 視圖創建了特定函數這一事實之外,我們還模塊化了我們的用戶界面代碼。

注意底部的module.exports = { loadAndShow, cleanup }行嗎? 這是一種讓我們準確指定導入此 UI 代碼的腳本可以使用哪些對象和函數的方法(並隱藏我們不想讓他們擔心的那些),這意味著我們現在有一個更有條理的 API 可以與之交互,顯示和破壞我們的 UI。

推薦閱讀在 Sketch 中釋放符號的全部潛力

讓我們看看這在實踐中是什麼樣子的。 返回index.js ,刪除舊代碼並添加以下內容:

 const UI = require("./ui"); function onRun(context){ UI.loadAndShow(context.scriptURL); };

我們正在使用 Sketch 自動提供給我們的特殊功能require來導入我們的ui.js代碼並將返回的模塊分配給UI變量。 這讓我們可以訪問一個簡化的 API 來觸發我們的用戶界面。 現在東西更整潔了,很容易找到!

結論

幹得好——你已經走了很遠! In the next part of this tutorial, we'll give our web UI the ability to send us a message when the “Apply” button is clicked, and we'll focus on the main plugin functionality: actually generating layer mosaics!