はじめに
ユーザーに特定の箇所以外操作してほしくない時ってありますよね?
素のcssとjavascriptだけで実装できる方法があるので健忘録としてまとめます。
実装例
仕組みとしては単純で、画面を覆う要素(ここではcover
としています)を作成し、clip-pathスタイルで指定箇所を切り抜くだけです。
See the Pen Untitled by rahhi555 (@rahhi555) on CodePen.
解説
一つずつ順を追って解説していきます
1. 全体を覆うdiv要素を作成して、全てのクリック操作を封じる
<button id="button1">button1</button>
<button id="button2">button2</button>
<!-- このdiv要素が最前面かつ画面いっぱいに広がることで、 クリックイベント等を吸い取ります -->
<div id="cover"></div>
/*
position: absolute だと親にposition持ちが存在した場合そっちに基準が向かうので,
position: fixed で親要素に関係なくhtml要素の4辺を基準にします
*/
#cover {
background-color: rgba(0,0,0,0.5);
position: fixed;
inset: 0;
z-index: 2147483647;
}
2. クリックさせたいボタンのパスを作成する
複数の座標を指定することで、コンピューターがその間を線で補完して図形を描写するベクタ形式と呼ばれる画像タイプがあります。
パスはベクタ形式で用いられる記法で、座標を絶対的に指定するほか、記述した座標から相対的に移動したポイントを指定することが可能です。
/** coverのパスと操作箇所(button)のパスを取得し、coverのclipPathスタイルに適用させる */
const setCoverClipPath = (button) => {
// getBoundingClientRectメソッドでスクリーンから見たボタンの位置4点を取得する
const { top, left, width, height } = button.getBoundingClientRect()
// 大文字は絶対位置で小文字は相対位置です
// M: 始点(x及びy), h: 水平に移動(x), v: 垂直に移動(y), z: Mへ戻る直線
const buttonPath = `M ${left} ${top} h ${width} v ${height} h -${width} z`
// 3に続く
3. coverのパスを2とは逆方向に作成する
ここが少し工夫をしなければならないポイントで、ドーナツ状のパスを作成するには時計回りと反時計回りのパスを合体させなければなりません(理由は知りません!)
というわけで2とは逆方向にcoverのパスを取得します。
// 2で書いた処理
// 表示されているスクリーンの幅と高さを取得する
const { clientWidth, clientHeight } = document.getElementById('cover')
// 2は右回転だったので左回転でパス作成
const coverPath = `M 0 0 v ${clientHeight} h ${clientWidth} v -${clientHeight} z`
// 4に続く
4. coverにclipPathスタイルを適用させる
作成した2つのパスを合体して適用させるだけです
// 3までの処理
cover.style.clipPath = `path('${buttonPath} ${coverPath}')`
}
5. 完成
/** coverのパスと操作箇所(button)のパスを取得し、coverのclipPathスタイルに適用させる */
const setCoverClipPath = (button) => {
// getBoundingClientRectメソッドでスクリーンから見たボタンの位置4点を取得する
const { top, left, width, height } = button.getBoundingClientRect()
// 大文字は絶対位置で小文字は相対位置です
// M: 始点(x及びy), h: 水平に移動(x), v: 垂直に移動(y), z: Mへ戻る直線
const buttonPath = `M ${left} ${top} h ${width} v ${height} h -${width} z`
// 表示されているスクリーンの幅と高さを取得する
const { clientWidth, clientHeight } = document.getElementById('cover')
// 2は右回転だったので左回転でパス作成
const coverPath = `M 0 0 v ${clientHeight} h ${clientWidth} v -${clientHeight} z`
cover.style.clipPath = `path('${buttonPath} ${coverPath}')`
}
ちなみにカバーしている範囲は表示している画面内のみなので、スクロールは無効にしたほうがいいと思います。
基本的なことはこれだけなので、vueやreactにも導入しやすいのではないでしょうか。
頑張れば実際に動かすことのできるチュートリアル等も作成することができます。
追記
https://introjs.com/
よくよく調べたら普通にライブラリありました。なんという車輪の再発明。
しかも中身を読んでいくと自分の実装より遥かにシンプルに書かれていました。
See the Pen changeTarget by rahhi555 (@rahhi555) on CodePen.
画面をカバーするdiv要素より高いz-indexを持ったクラスを付け替えるだけです。
ただしスタックコンテキストの関係で常にカバー要素をターゲットと同じ階層に置かなければ正常に順序付けされない可能性があるので、そういったしがらみから逃れたい場合に紹介した方法が役に立つかもしれません。