あなたが正しくウェブページのカーソル形状を変更するには
photo credit: Maria Eklind Frankie & Benjys bookstore and theatre Reggiano via photopin (license)
なにやら暑苦しいタイトルで失礼します。
ここにはウェブページのマウスカーソルの形状を変更するにはどうすればってことを書いてます。
「何を今さら」って感じですけど、先日、目からウロコが3つほど落ちましたので書いておきます。
小ネタといえば小ネタです。
ページ全体のカーソル形状を変更する
ページ全体でマウスカーソルの形状を「待ち状態」にしたいことがありますね。 勝手に手が動くぐらいの勢いで以下のようにしていましたし、ググってみてもこのように説明されてるページが多いです。
でも、これ厳密には不完全なんですよ。
document.body.style.cursor = "wait";
何がダメかって言うと、WEBページのコンテンツが短い場合です。
コンテンツが少ないと、画面の下のほうにカーソル形状が変化しない領域が残ってしまうのです。
BODY要素(=document.body
)はブラウザ画面の全体に広がっているとは限らないということです。
スタイルシートがどうなってるかにもよりますけどね。
で、これを回避するのは実に簡単。BODYじゃなくてHTMLのスタイルを変更します。
document.body.parentElement.style.cursor = "wait";
document.bodyの親要素はHTMLです。document.documentElement
もHTML要素を指すそうですが、ブラウザ間の実装に違いがあるかもしれないので注意が必要。
以下のボタンを押すとHTMLのカーソル形状を変化させます。
コントロールは親要素のカーソル形状を継承しない
下のフォームのボタンを押すとBODYのカーソル形状をWaitカーソルに変えます(5秒後に元に戻ります)が、 各コントロールの上へカーソルを持って行っても形状は変化していません。 ChromeやFirefoxではLABELのカーソル形状も変化しません(Edgeでは変化しました)。
既定の形状に戻すのはdefault
ではない
一時的に変化させたカーソル形状を元に戻す場合、default
に設定しなおしていましたが、これでは全てが矢印になっていました。
ほとんどの要素の既定の形状は矢印ですが、そうでないのもありまして。
例えばテキストボックス(<input type="text"/>
)は縦線みたいなのが既定の形状。
const foo = getElementById("foo"); foo.style.cursor = "wait"; (長めの処理) foo.style.cursor = "default"; //← 全部矢印になっちゃいます
といっても、すべての要素の既定のカーソル形状を覚えておいて、それぞれ個別に元に戻すなんてナンセンス。
じゃ、どうするか。default
じゃなくauto
にすればよいのです。
const foo = getElementById("foo"); foo.style.cursor = "wait"; (長めの処理) foo.style.cursor = "auto"; // ← 要素ごとの既定のカーソルに戻ります。
ちなみに、最近のブラウザでは initial でも元に戻っているようです。
これはHTML5で規定されているのかもしれませんが、IE11では効きませんから、やっぱり auto
でよいみたいです。
default / initial / auto でそれぞれ意味が違うのでしょうから、ややこしいですね。
EdgeがIMGタグのSVGをストレッチしてくれないことがある
HTMLのIMGタグでSVG画像を表示するとき、EdgeではSVG画像がIMGタグのサイズに拡大・縮小されず、SVGで指定されたサイズでそのまま表示されることがあります。
他のブラウザ(Chrome、Firefox、Safari)では大丈夫でした。 また他の画像形式ならばEdgeでも問題ありません。
どうやらEdgeでは、SVGのサイズ指定方法によって表示が変わるようです。これが不具合なのかどうなのかはわかりません。
このようにEdgeはちょっと邪魔くさいことが多く、ブラウザシェアも6%程度(2018年9月現在の日本国内シェア)のようなので、できれば無視したいのですが、Windows 10の標準バンドル品なので、無視しがたいのが悩み所・・・。
※ 本記事で確認しているEdgeのバージョンは42。レンダリングエンジンEdgeHTMLのバージョンは17。
SVG関連の記事:
発生する現象
下の画像は、9×9のクロスハッチで、SVGのサイズは1215×1215pxのSVG画像です。 Edge以外では正しく表示されているはず。
gray-9x9-crosshatch-1215x1215.svg:
<img width="270px" src="https://takamin.github.io/images/gray-9x9-crosshatch-1215x1215.svg"/>
Edgeでは下のように表示されてしまいます。 IMGタグの領域内にオリジナルのスケールで表示されています。
原因
SVGファイル内での画像サイズの指定がstyle属性で行われていると、(Edgeだけで)この現象が発生するようです。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" style="width:1215px; height:1215px;" viewBox="0 0 321.46874 321.46876" version="1.1" . . .
対策
SVGファイルのSVG要素のサイズ指定をstyleではなくwidthと height で行えばEdgeでも正しく表示してくれます。
※ widthとheight属性でサイズ指定していても、styleでサイズが指定されていれば、スケーリングされないようです。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="1215px" height="1215px" viewBox="0 0 71.437498 71.437502" version="1.1" . . .
以下は属性だけでサイズ指定したSVGです。Edgeでも正しく表示されているはずですよ。
<img width="270px" src="https://takamin.github.io/images/gray-9x9-crosshatch-1215x1215-edge.svg"/>
他の対策
他の対策もありますので以下に書いておきますね。
style属性を書き換える
SVGのstyle属性でサイズを指定していても、表示したいサイズに変更すれば問題ありません。 しかしこれだと、大きなサイズの画像を縮小してサムネイル的に表示したいときなどに、ファイルを分ける必要があり、SVGを使っている利点が出ません。
XHRでSVGを取り出して表示。
外部サイトのSVGファイルなどで編集できない場合などは、別途XHRでSVGを取り出して内容を編集してから表示する必要があるかもしれません。 この場合は、IMGタグではなくインラインSVG(SVGタグ)で表示する手もあります。
何が正しいのかよくわからん
この挙動が、HTMLの仕様として正しいのかそうでもないのかは読み取れませんでした。 しかし、SVGの仕様では、style属性の中でwidthやheightを指定できるという記述は見つけられませんでした。
つまり、本来widthやheightはstyleで指定するものではないけれど、Edgeはそれを(良かれと思って?)解釈してくれて、上記のような挙動になっているのかもしれません。
リンク
ウェブアプリからGoogle Driveのファイルを扱う 'gdrive-fs'
photo credit: suzyhazelwood DSC04299-02 via photopin (license)
ウェブアプリから Google Drive のファイルを操作するモジュール(npm)を公開しましたので、ご紹介。
npmはこちら。
関連記事:
機能概要
サインインしたユーザーのファイルを読み書き
このモジュールを利用するウェブアプリで、ユーザーがGoogleアカウントでサインインすると、ウェブアプリからユーザー自身のGoogle Driveのファイルへアクセスできます。
これらのファイルは、ユーザーが特別に共有設定などをしない限り、他人には見ることができません(ただしアプリはDrive内の全内容を把握できることには注意が必要)。
ファイル名によるファイルの操作
ローカルのファイルシステムと同じように、ファイル名とフォルダ名によってファイルを扱うAPIを提供しています。
Google Drive API は、ファイルIDによってファイルにアクセスしますが、パスでアクセスできるほうが直感的ですよね。
各APIは、おおまかに Node.jsの fs モジュールのメソッド名に従っています。 ただし、多くが非同期関数として動作しますが、callback は受け付けず、Promiseを返します(ほとんどが async 関数です)。
UI作成支援機能
こちらはまだα版。動作確認用のサンプル実装程度です。
そのうちまじめに作って公開するつもりなのでちょっと待ってネ。
インストールと使い方
npmで公開しているので、npmでinstallしてください。
$ npm install --save gdrive-fs
バンドラーを使われているなら、CommonJS的にインポートしてください。
const Gdfs = require("gdrive-fs"); (async()=>{ await Gdfs.loadApi(<clientId>, <apiKey>); . . . })();
バンドラーを使っていない場合、HTMLのScriptタグで 本モジュールの/build/gdrive-fs.min.js
を読み込んでください。
この場合、グローバル変数として、Gdfsが利用できます。
<!DOCTYPE html> <html> <head>...</head> <body> . . . <script src="<path-to-module>/build/gdrive-fs.min.js"> (async()=>{ await Gdfs.loadApi(<clientId>, <apiKey>); . . . })(); </script> </body> </html>
動作条件
「APIキー」と「クライアント ID」
ウェブアプリ開発者は、Google Developer Console でプロジェクトを作成し、ウェブアプリケーション向けの認証情報として、「APIキー」と「OAuth 2.0のクライアント ID」を作成し、適切に設定しておく必要があります。
多くの場合、APIキーはHTTPリファラーによる制限をかけ、クライアントIDのタイプは「ウェブアプリケーション」としておきます。
APIリファレンス
本モジュールが提供しているクラス、APIの完全な説明は、以下のリンクからどうぞ。 つたない英文で書いています。
以下に要約した内容を転載します。最新情報は上記URLを参照してください。
Gdfsクラス
このクラスは Google Drive API v3 へのインターフェースであり、インスタンスはカレントディレクトリを管理して、ファイルやフォルダを操作するメソッドを提供します。
このインスタンスを作成する前に、これを利用するアプリケーションの Client Idと Api Keyにより、クラスメソッド loadApi
で、このAPI群が読み込まれていなければなりません。
これら2つのキーは、Google Developer Consoleのプロジェクトで作成されていなくてはなりません。
また、ファイルを操作するにはユーザーがGoogleアカウントでサインインしておく必要があります。
このインスタンスのカレントディレクトリは、コンストラクタでルートフォルダに初期化されます。
これはchdir
メソッドで変更できます。
カレントディレクトリ変更時には、oncwdupdate
コールバックが呼ばれます。カレントディレクトリを知るには、cwd
メソッドが利用できます。
クラスメソッド
async loadApi(clientId, apiKey)
- Google Drive API (v3)を読み込む。isSignedIn()
- Google Driveにサインインしているかどうかの確認。async signIn()
- Google Driveにサインインする。async signOut()
- Google Driveからサインアウトする。
コンストラクタ
Gdfs()
- コンストラクタ
インスタンスメソッド
async chdir(directory:string)
- カレントディレクトリの移動。cwd()
- カレントディレクトリを返す。async isDirectory(path:string)
- パスがディレクトリかどうかを返す。async mkdir(path:string)
- ディレクトリの作成。async readdir(path:string, options:object)
- ディレクトリ内のファイルのリストを読み出す。async readFile(path:string)
- ファイル内容の読み取り。async rmdir(path:string)
- ディレクトリ削除。async stat(path:string)
- ファイルのメタ情報を取得する。async unlink(path:string)
- ファイルの削除(ゴミ箱には入らない)async writeFile(path:string, mimeType:string, data:any)
- ファイルへの書き込み。または新規作成。
制限事項・注意事項
名前だけではファイルを特定できない問題は無視
Google Driveでは、一つのフォルダ内に同じ名前のファイルが複数存在できます。 なので、本当は名前だけではファイルを一意に特定できません。 でも、そういう使い方自体が混乱のもとですから、本モジュールではバッサリ無視して最初に見つけたほうを採用しています(多分)。
削除してもゴミ箱には入りません
Gdfs.unlink(path)
メソッド で削除した場合、ごみ箱には入りませんので、重々気を付けてくださいね。
セキュリティ警告とアプリケーションの申請について
未申請のアプリケーションに、ユーザーがサインインしようとすると「安全でないアプリケーションである」との警告が表示されます。 Googleにアプリケーションを申請して承認されていれば、これは表示されなくなるはずです。 アプリケーションを正式に公開する場合は申請しておいたほうが良いでしょう。
アプリケーションの申請はそれぞれのエンドポイントで行う必要があるため、本モジュールで行うものではありません。
ライセンス
本モジュールはMITライセンスにおいて公開しています。
免責
本モジュールは、これを利用するアプリケーションの安全性を保証せず、その動作について一切の責任を負いません。 本モジュールを利用するアプリケーションのすべての動作は、アプリケーションの製造者の責任において試験され、ユーザーに提供されるべきです。 悪意のあるアプリケーションや不具合によって被害を受けないためには、信頼できる人以外が作ったアプリケーションには接続するべきではありません。
SVGでツールチップを表示する
WEBページのインラインSVGにツールチップを表示させる方法は、HTMLとは違うので、以下に両方の方法を書いています。
ツールチップは、画面の要素にマウスポインタを乗せてしばらくすると、説明文などが表示される小さなボックスのことですね。
HTMLの場合
HTMLでは「title属性(Attribute)」です。 title属性に表示したい内容を設定しておけば、マウスポインタを乗せてしばらくすると、ツールチップが表示されます。
以下のHTMLで描いたボックスにマウスを乗せると・・・
HTMLでは「title属性(Attribute)」に設定します。
ソース:
<span style="border-radius:5px; background-color: #080; color:white; font-weight:bold; padding: 1em 2em;" class="tooltip-container" title="これはTITLE属性の内容です。"> HTMLでは「title属性(Attribute)」に設定します。 </span>
SVGの場合
しかし、SVGにはtitle属性がありません。 「じゃどうするか?」って調べてみると「SVGでは『title要素』を使いましょう」とのことでした。
以下のインラインSVGで描いた図形にマウスを乗せるとSVGのツールチップが表示されます。
ソース:
<svg viewport="0 0 400 100" width="100%"> <g> <title>これはSVGのタイトル要素。</title> <rect x="0" y="0" width="400" height="100" fill="#080" rx="5" ry="5"/> <rect x="30" y="20" width="50" height="50" fill="#008" rx="5" ry="5"/> <rect x="70" y="45" width="30" height="30" fill="#088" rx="5" ry="5"/> <rect x="20" y="50" width="40" height="40" fill="#800" rx="5" ry="5"/> <circle cx="340" cy="75" r="20" fill="#880"/> <circle cx="310" cy="40" r="40" fill="#808"/> <circle cx="350" cy="32" r="30" fill="#088"/> <text x="25" y="80" width="400" fill="white" font-weight="bold" >SVGではTITLE要素に設定しますの・・・</text> </g> </svg>
JavaScriptではツールチップの内容は、title要素のtextContentに設定します。
ParcelのBabelでAsync関数を変換せずに使う設定
前回記事で、ParselでAsync関数が普通の関数に変換されてしまって困っていましたが回避する方法が分かりました(実は困らなくてよかったのですが、それは後ほど)。
単にワタシが知らないだけで、普通にBabelの設定でした。 Parcelはまったく無関係。 まあ誰でも最初はビギナーですし。 いくつになってもお勉強。
前回の記事:
最新ブラウザをターゲットに指定するだけ
以下の .babelrc
を使えばAsync関数は無変換でそのまま使用されますね。
.babelrc
{ "presets": [ ["env", { "targets": { "browsers": ["last 1 Chrome version"] } }] ] }
しかし、これって「ターゲットブラウザはChromeの最新版」ってだけの設定ですよね。 Chromeの最新版が Async/Await に対応しているからってことなのでしょう。
Async/Await対応ブラウザ全部乗せ
他のブラウザ、FirefoxやEdgeでもちゃんと動作するのか不安な場合は、以下のようにAsync/Await対応のブラウザを全部並べておけばよいはずです。 そうすることで各ブラウザ間の差も吸収してくれるはず(今やほとんど存在しないかもしれませんが)。 ちなみに、ここにIEを入れるとAsync/Awaitに非対応ですからPromiseを使った処理に変換されます。
.babelrc
{ "presets": [ ["env", { "targets": { "browsers": [ "last 1 Chrome version", "last 1 Firefox version", "last 1 Edge version", "last 1 Safari version", "last 1 Opera version" ] } }] ] }
Babel関係のモジュールも不要になります
前回の記事で必要と書いていた babel関係の2つのモジュールも、上の設定では一切不要になりますのでアンインストールして大丈夫。
$ npm uninstall --save-dev babel-runtime babel-plugin-transform-runtime
Polyfillされても困る必要はなかったのだ
なんでAsync関数が普通の関数に変換されて困っていたかというと、関数が非同期であるかどうかを、関数のコンストラクタで判定しようとしていたからなのですが、そもそもそれが間違いでした。
関数オブジェクトのコンストラクタがAsyncFunction
か Function
かは同期/非同期に無関係ではありませんが、正しい判定はできません。
Asyncでない普通の関数がPromiseを返してもよいのですから。
関数が非同期かどうかの判定は、その関数を実際に呼び出して「戻り値がPromiseであるかどうかで判断すべき」なんですね。
つまり、今さらですけど、コンストラクタで困る必要はありませんでした。 大騒ぎして失礼こきました。