はじめに
こちらのライブラリは別の記事でも紹介している通り、Safariのタブバーの裏にもコンテンツを描画することを可能にするためのものです。
人生で初めてライブラリというものを作り、かつそれをnpmに公開したのでその備忘録的なものでもあります。
依存ゼロ、フレームワーク非依存、JS は gzip で約 2.8kB + スタイルシート1枚です。
インストール方法
npm i under-liquid-glass
直接見にいく場合
- npm: https://www.npmjs.com/package/under-liquid-glass
- GitHub: https://github.com/ocean6954/under-liquid-glass
何ができるか
タブバーの裏には、その座標にあるドキュメントの中身がそのまま映ります。普通のページだとそこに何を映すかは自由に決めることはできません。スクロールの途中なら、たまたまその位置にあったものが映りますし、上端や下端ではキャンバスの色で塗りつぶされます。
このライブラリはその領域をこちらの管理下に置きます。ドキュメントを常に端から離した位置に留めておくので、バーの裏に出したいものがどのスクロール位置でもそのまま出ます。
仕組みそのものは別記事に全部書いたので、「なぜこれで描けるのか」を知りたい方はそちらをご覧いただければと思います!
仕組みの記事と重複してる部分もあるかと思いますがその点はご了承ください🙇♂️
こちらの記事では、その仕組みを使うために必要なことだけを書きます。
使い方
1. マークアップ
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<body>
<div data-ulg-scroller>
<div data-ulg-backdrop><!-- ページ全体の背景はここ --></div>
<div data-ulg-surface>
<!-- アプリ本体 -->
</div>
</div>
</body>
属性で役割を伝えると、高さや余白の計算はライブラリ内のCSSがやります。
| 属性 | 役割 | 必要性 |
|---|---|---|
data-ulg-scroller |
実際にスクロールする要素 | 必須 |
data-ulg-surface |
中身を入れる要素。scroller の中に置く | 必須 |
data-ulg-backdrop |
ページ全体の背景。scroller の直下、surface より前に置く | 任意 |
data-ulg-overlay |
全画面オーバーレイ。置き場所は backdrop と同じ | 任意 |
viewport-fit=cover は縦向きのバーに対しては何もしませんが、横向きでカメラ部分の外までページを広げるのに必要なので、付けたままにしておいても良いでしょう。
2. 呼び出し
import 'under-liquid-glass/style.css'
import { underLiquidGlass } from 'under-liquid-glass'
const glass = underLiquidGlass({
edgeSpace: 140,
})
edgeSpace は、バー1枚分として確保する余白の量です。この値がコンテンツの上下に1つずつ入るので、ドキュメントはビューポートより edgeSpace × 2 だけ高くなります。タブバーが覆う量より大きければよく、iPhone で実測した範囲では 60〜120px でした。デフォルトは少し余裕を持って140pxに設定してあります。
呼ぶタイミングは「DOM ができたあと、かつ自分のコードがスクロール位置を読み書きするより前」です。フレームワークだと最初の onMounted とは限らない(子が先にマウントされる)ので、自分のスクロール処理との前後だけ気にしてあげると良いと思います。
3. 背景を描く場所を1か所に寄せる
ドキュメントの中身は全部バーまで届くので、背景をいろんな場所で定義していると、どれが映っているのか分かりづらくなります。重なり順とかも気にしないといけないので。
なのでページ全体の背景は [data-ulg-backdrop] に置いて、[data-ulg-surface] では定義しないのがおすすめです。そうすると色でも画像でも動画でも、置き場所は同じで済みます。
セクションごとの背景だけは例外で、これはページ全体のものではないのでセクション自身に定義させる必要があります。先頭と末尾のセクションの背景は、何もしなくてもバーの裏まで伸びます(透明なボーダーで箱だけ広げています)。
API
よく使うのはこのあたりです。
| メンバー | 説明 |
|---|---|
controller.scroller |
実際にスクロールしている要素。仕組みが有効でないときは null。controller.scroller ?? window と書くのを推奨 |
controller.scrollTo(target) |
要素を表示領域の先頭まで持ってくる |
controller.state |
'pinned' / 'unlocked' / 'inactive'
|
controller.refresh() / destroy()
|
測り直す / 元に戻す |
options.enabled |
false で普通のページのまま |
options.onStateChange |
状態が変わったときに呼ばれる |
CSS からは、仕組みが有効でないときも含めて正しい値が取れるカスタムプロパティを公開しています。
| プロパティ | 値 |
|---|---|
--ulg-edge-space |
実際に使われている余白。仕組みが無効のときは 0px
|
--ulg-viewport-height |
バーに隠されない高さ |
--ulg-visible-height |
バーの裏も含めた、見えている高さ |
気をつけること
実際に組んでいて引っかかったのは、だいたいこの3つです。
-
window.scrollYを動かさない。
ページ内アンカー、scrollIntoView、フォーカス時の自動スクロールは全部ドキュメントを動かすので、居座り処理に打ち消されます。controller.scrollTo()を使ってください。 -
position: fixedではバーの裏に描かれない。
サイズをどう指定しても駄目でした。全画面の層が要るときはdata-ulg-overlayを使ってください。 -
祖先の
overflow: hiddenが sticky を打ち消す。
body { overflow-x: hidden }とか割とありがち。
この他の細かい落とし穴(スクロールライブラリとの併用、先頭要素の高さ計算、scroll-behavior: smooth)は README にまとめてあるので、そちらを AI に読み込ませてあげてください。
表示が変だと思ったとき
デバッグ用のパネルを別エントリポイントで同梱しています。
import { attachDiagnostics } from 'under-liquid-glass/debug'
if (import.meta.env.DEV) attachDiagnostics(glass)
実機の実測値が出るほか、「壊れてはいないが変に見える」状態(scroller がスクロールコンテナでなくなっている、surface が backdrop を塗りつぶしている、sticky の層の位置が違う)を名指ししてくれます。バーの裏に読める文字を描く機能もあるので、ステータスバー越しに TOP の字が読めれば、コンテンツは届いていて、問題は端末の透明度設定のほうだと切り分けられます。
本番のバンドルには入りません。自分のビルドを実機で動かせない場合向けに、diag.html も単体で同梱しています。
動作しない環境について
このライブラリは iOS Safari でだけ動きます。それ以外では普通のページに戻ります。
というより、Safari だけに適用されるように設定してあるので、条件に合わない環境では最初から何も適用されません。Safari 以外では戻すより、Safari だけに適用する方が楽だと思ったので。
あと、バーの透明度はiPhone端末側のユーザー設定でも変化します。「透明度を下げる」がオンになっていると、バーが不透明になります。こればかりは端末側で変更するしかありません。
このAI時代になぜライブラリにしたのか
ここからはなぜ今回ライブラリにしようと考えたのかについてを書いていきます。ほぼエッセイみたいな感じです。
1. 他の人の役に立つ機会があればいいなと思ったので
弊社が請け負う案件では全く別のものもあれば、似たような案件もあります。開発期間は短めなものもあります。その中で同じような要望が出てきた場合、「このライブラリ使って」というだけですぐに対応できる利便さがあると考えたからです(ライブラリなので当たり前かもしれませんが)。
READMEをLLM用に用意してAIに読み込ませて使ってもらうというのも考えましたが、長ったらしいドキュメントを読んで結果ばらつきが出るよりは、パッケージ化されたものを渡す方が楽なのは間違いないので。
あと、シンプルに説明しづらいです。
口頭で説明するのも文章で説明するのもどちらもわかりづらいなと個人的には思います。
もちろんチームメンバー以外にも世の中で同じような要求に当たった方の役に立てればいいなと思います。特に今回のケースは日本語の資料みたいなのがなかったので、世の中の声も顔も年齢もわかりませんが、そういう困ってる人たちの助けになれば嬉しいです。
2. AI実装で4時間くらいかかった
これは書いてある通りなのですが、Claude Codeを使用して実装しても4時間くらいかかりました。
もちろん、自分の技術力でこのくらいかかったというだけなので、他の人ならもっと早く形にできる可能性はあると思いますが。
途中、Claudeに「無理です。諦めて方向転換しましょう」みたいなことを言われたくらいです。
ここで僕のあまのじゃく精神にちょっと火がついたのはありますが、できないわけがないと思って無理やり実装に至りました。
スクロールを分担するのも「無理」と言われましたがこちらはすんなりいけました。
そもそも先人の記事もほとんどなかったのと、それ用のプロパティもなかったのでClaudeを以てしても拒否られるという始末になっていたのだと思います。
そもそもタブバーの裏に最初から何も描画できないのなら自分も諦めてたかもしれませんが、スクロール中の見た目とかはタブバーの裏にも描画されてる瞬間がありました。
途中にそういう過程があるのなら、できないわけがないと思ったという感じです。
諦めなくてよかった。
3. シンプルにそういうの作ってみたかった
これは完全に自己満なのですが、やっぱりエンジニアとして生きていくならライブラリとかOSSとかそういうの憧れるじゃないですか。なので一回やってみたかったので今回ライブラリ作成までしてみました。使われたら嬉しいのもありますが、実際に形にして公開できたというだけで嬉しかったりします。
4. ハードルを下げたかった
ここは見出しの「このAI時代になぜライブラリにしたのか」とも関わりが深い部分ですが、行動のハードルを下げたかったというのがあります。
どういうことかと言いますと、
まず、「AIなしで進める従来のやり方」のハードルを100としましょう。
次に、「やったことはないがAIを使えばすぐできるでしょ」のハードルを10とします。
そして今回のように「AIを使って実際にやってみて、なんとなく手順もわかる」のハードルを1とします。
これらの数字は私が勝手につけてるだけなので実際にどうとかはわかりません。人によっては全部1の人もいるかもしれません。
ここで言いたいのは、AIを使ってでも一回経験してしまえば次のハードルがグッと下がるということです。
自分の好きな言葉の一つに、アメリカのフリークライマーであるトッド・スキナーさんが遺した言葉があります。
一歩踏み出せるなら、もう一歩も踏み出せる
If you can take one step, you can take one more.
AIはこの最初の一歩を超超超踏み出しやすくさせてくれるものだと私は思います。
そしてこの一歩が踏み出せた時に、次にもつながりますし、他の物事にも繋がる余白が生まれると自分は思っています。
他のことを試そうと思えるし、今までやってきたことをさらによくすることもできる、行動のハードルを下げるメリットはこのような価値も生み出すと思います。
なので今回初めてライブラリ作成をしたことによって、次にライブラリ作成するときのハードルがグッと下がり気軽さが上がるし、もしかしたらOSS開発、コントリビュートなんてやっちゃってるかもしれません。
なのでこのマインドは引き続き大切にしていきたいと思います。
5. 正式に実装されたら嬉しい
これが一番大きい理由かもしれません。
もう一つの記事でも書きましたが、自分はこのライブラリがもたらしてくれる効果を結構気に入ってるんですよね。
見た目がいいです。特に背景が動くものだと尚良い。
ブラウザのUIが占める領域が少なくなるのでWebサイトに没入感が生まれます。
なので私的にはこの機能は、デフォルトで備わっていてくれたら嬉しいなと思っています。
今はまだないので開発側で誤魔化しながらやるしかない。
だからまず、ライブラリとして置いてみる。
もしかしたらめっちゃ需要があってたくさん使われるかもしれないし、全く使われないかもしれない。
たくさん使われたなら、それは需要がある証拠になる。
逆にあまり使われてなくても、誰かの役に立ってればそれはそれでよし。
他の人が使う中で、改善点とか、そもそももっと良いものを作ってくれる人が現れるかもしれない。
そしてこれらがブラウザベンダー(Apple / WebKit チーム)の目に留まり、正式に実装してくれるかもしれない。
Safari の既定の挙動になれば他のブラウザも追従してきて、ブラウザのスタンダードになるかもしれない。
というようなことを自分は考えていました。この中のどれが実際に起こって、どこまでいくのかはわかりません。
全く波風が立たないこともあるでしょう。
ですが、私はこのような正のバタフライエフェクトのようなものを期待しています。
これが、今回私がライブラリ化した1番の理由です。
おわりに
公開したあとに自分で触っていて、なんだかんだ愛着が湧いています。Safari の見た目が全画面になるのは気に入っていて、個人的にはこの状態のほうが好きです。今後もし自分で Web のコンテンツを作るなら、普通に使っていくと思います。
弊社では Vue を主に使っているので、ゆくゆくは Vue 向けのコンポーネントも作ってみたいです。
今回は人生で初めてライブラリを公開した感想記事的なものを書きました。
このライブラリがもたらす影響は0かもしれませんが、この世界のどこかの誰かの役に立ってくれたらいいなというのはずっと思っています。
ありがとうございました。
