この投稿では、HTMLでmacOSのオリジナルのスクリーンセーバを作れる方法を紹介します。
macOSのスクリーンセーバを作ってみたいけど、SwiftもObjective-Cも分からない、分かるのはHTMLやCSS、JavaScriptなどウェブ関連技術だけ、というウェブ系プログラマむけの解説です。
ちなみに僕は、この投稿で紹介する方法で、JavaScriptの人気ライブラリをひたすら紹介するスクリーンセーバを作りました:
JavaScriptの人気ライブラリを紹介しまくるスクリーンセーバが完成した😌
— suin❄️TypeScript入門書執筆中 (@suin) April 30, 2020
需要があれば公開するかも pic.twitter.com/tOnUhMIbh8
Swiftなどを知らなくても、CSSやJavaScriptでアニメーションを頑張れば、こういった動きのあるスクリーンセーバが作れるわけです。
スクリーンセーバをHTMLで作る手順
Xcodeで新規プロジェクトを作る
まず最初に、スクリーンセーバのプロジェクトの下準備をしていきます。
Xcodeを起動して、新規プロジェクトを作ります。

その際、「Screen Saver」のテンプレートを選びます:

「Product Name」「Organization Name」「Organization Identifier」は適当に:

Swiftの接着剤コードを準備する
macOSは標準でHTMLだけでスクリーンセーバが作れるわけではないので、HTMLをスクリーンセーバ上に描画するためのコードは、Swiftで書いておく必要があります。
ここでは、スクリーンセーバとHTMLの接着剤となるSwiftのコードを用意する手順を説明していきます。
まず、先程新規作成したプロジェクトから、不要なObjective-Cのファイルを消します:

代わりに、消したファイルと同じファイル名 + .swift
で空のファイルを作ります。「MyScreenSaver」を右クリックして「New File」をクリックします。

ファイル形式は「Swift File」にします:

ファイル名は「MyScreenSaverView.swift」にします:

作成時に↓と聞かれますが、「Don't Create」を選びます:

Swiftファイルが作成されたら、デフォルトで入っているコードは消し、下記コードをコピペして上書き保存します:
import ScreenSaver
import WebKit
class MyScreenSaverView: ScreenSaverView, WKNavigationDelegate {
private var webView: WKWebView!
convenience init() {
self.init(frame: .zero, isPreview: false)
}
override init!(frame: NSRect, isPreview: Bool) {
super.init(frame: frame, isPreview: isPreview)
setupWebView()
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
setupWebView()
}
private func setupWebView() {
let webConfiguration = WKWebViewConfiguration()
// webkitがローカルファイルを参照できるようにする設定
webConfiguration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
// webViewをスクリーンの大きさに合わせる
webView = WKWebView(frame: NSRect(x: 0, y: 0, width: frame.width, height: frame.height), configuration: webConfiguration)
webView.navigationDelegate = self
// webViewの背景を透明にする
webView.setValue(false, forKey: "drawsBackground")
// スワイプでの戻る/進むを無効にする
webView.allowsBackForwardNavigationGestures = false
// ピンチで拡大/縮小できる機能を無効にする
webView.allowsMagnification = false
// ローカルのHTMLを読み込む
if let htmlPath = Bundle(for: type(of: self)).path(forResource: "html", ofType: nil) {
let htmlUrl = URL(fileURLWithPath: htmlPath, isDirectory: true)
let indexUrl = URL(fileURLWithPath: htmlPath + "/index.html", isDirectory: false)
webView.loadFileURL(indexUrl, allowingReadAccessTo: htmlUrl)
}
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// ページが遷移したタイミングでwebViewをスクリーンセーバに表示する
self.addSubview(webView)
}
// マウスが動いたときにスクリーンセーバが解除されるようにする(その1)
override func hitTest(_ aPoint: NSPoint) -> NSView? {
self
}
// マウスが動いたときにスクリーンセーバが解除されるようにする(その2)
override var acceptsFirstResponder: Bool {
true
}
}
このSwiftコードが何をしているか簡単に説明すると、スクリーンセーバが起動したらWebView(埋め込み版のSafariのようなもの)を起動し、html/index.html
を読み込み、それをスクリーンセーバ上に表示するといった処理が書かれています。
Swift側の準備は以上です。
HTMLファイルを作る
次に、スクリーンセーバのグラフィックデザインを実装していく場所であるindex.html
を作っていきます。
まず、html
フォルダを作りたいので、「MyScreenSaver」フォルダを右クリックして「Add Files to "MyScreenSaver"」をクリックします:

すると、Finderのダイアログが出てくるので、「New Folder」をクリックし、「html」フォルダを作ったら「Add」をクリックして閉じます。

次に、index.htmlを作るために、htmlフォルダを右クリック、メニューから「New File」を選びます:

「File」というファイルが作られるので、「index.html」に名前を変えておきます。
index.htmlの中身は適当に何か書いておきます:
<style>
h1 {
color: black;
background: white;
font-size: 30vh;
animation: blink 1s step-end infinite;
}
@keyframes blink {
75% { opacity: 0.0; }
}
</style>
<h1>Hello World</h1>
以上でHTMLファイルの準備は完了です。
スクリーンセーバを実行できるように設定する
続いて、スクリーンセーバを実行するための設定をしていきます。
Xcodeのメニューバーにある「MyScreenSaver」をクリックするとメニューが出るので、そこから「Edit Scheme」を選びます:

「Run」の「Info」タブの「Executable」セレクトメニューから「Other」を選び、ScreenSaverEngine.appに設定します:

ScreenSaverEngine.appは/System/Library/CoreServices/ScreenSaverEngine.app
にあります。macOSのバージョンによって、ありかが異なる場合があります。
次に、「Arguments」タブを開き、
-window
-module "$(EXECUTABLE_NAME)"
の引数設定を追加します:

更に、実行ごとにスクリーンセーバを自動的にインストールするように設定します:

open -W "${CODESIGNING_FOLDER_PATH}"
以上で、スクリーンセーバを実行するための設定は完了です。
スクリーンセーバを実行してみる
準備が整ったので、スクリーンセーバを実行してみましょう。
スクリーンセーバを実行するには、Command+Rを押すか、XcodeのRunボタン(▶アイコン)を押します。
実行が開始すると、設定アプリが立ち上がり、スクリーンセーバをインストールするか聞かれるので「Install」を選択します:

すると、スクリーンセーバがインストールされ、スクリーンセーバの選択画面にプレビューが表示されるようになります。

この状態で、設定アプリを閉じると、今度はスクリーンセーバを確認するためのウィンドウが表示されます:

index.htmlに書いた内容が表示されるはずです。
あとは、index.htmlを作り込んでいくと、オリジナルのスクリーンセーバが作れます。
ちなみに、実行したときに、インストールされたスクリーンセーバは、実行が終了しても残るので、設定アプリで自分が作ったスクリーンセーバを選択しておけば、Xcodeを閉じてもそのスクリーンセーバが使われるようになります。
Safariを使った動作確認がおすすめ
HTMLを直してはXcodeの「Run」でスクリーンセーバをインストールし直して確認する、というのは開発体験としてかったるいので、HTML側にごりごり書いていくときはSafariで動作確認するのをおすすめします。Safariを使うと、ウェブインスペクタも使えるのでデバッグがはかどります。
ChromeやFirefoxではなくSafariを使う理由は、Swiftのコードで使っているWKWebView
が他ブラウザの環境よりSafariに近いからです。
普段、Swiftを書かない人は、Xcodeも慣れていないと思うので、HTMLをいじるときはVSCodeなど自分の好きなエディタを使ってもいいです。
Safariで動作確認するときは、html/index.html
を開くだけです。HTMLを直したら、リロードする。普段のウェブアプリの開発と同じ要領です。
スクリーンセーバの作りにもよりますが、fetch()
などで外部APIを叩いたりするときは、Same Origin Policyにひっかかってしまうことがあります。その場合は、Safariの「Devloper」→「Disable Cross-Origin Restrictions」をオンにしておくと良いです:

また、フルスクリーンでの動作確認をするときは、「View」→「Always Show Toolbar in Full Screen」のチェックを外しておくと良いです:

おわり
macOSのスクリーンセーバを作るのに、Swiftの知識はほぼ不要です。
慣れ親しんだHTMLやJavaScriptを駆使して、自分だけのスクリーンセーバを作ってみてはいかがでしょうか。
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローしてもらえると嬉しいです→Twitter@suin