ドラッグ可能なHTMLダイアログ(pure javascript)

 ファイルを開くダイアログならともかく、Itemの回転ダイアログなどは画面に被っていると、編集対象が隠れてダイアログ操作しずらくなってしまいます。

そこでモーダルダイアログ をドラッグで移動できるようにする必要があります。

HTMLのダイアログの場合、ドラッグ可能にするにはJSが必要で少しばかり手間です。

 


 

 

こちらに一案が紹介されていますが、これが動かなかったので別案を立てたものです。

 ドラッグで移動できるdialogタグをざっくりと作る

たぶんdialogやその親ページにかけられているCSSやHTML構造によって異なるのでしょう。

 

JS,CSS,HTML共にバニラな状態で動くはずです。

HTML

<dialog id='doc-save-dialog' draggable="true">
<form method="dialog">
<h1>Save?</h1>
document name:<br>
<input id='doc-name-input' type='text' class="default-focus-in-dialog"/>
<menu>
<button value="cancel">Cancel</button>
<button value="Save">Save</button>
</menu>
</form>
</dialog>

 大事なのはこれです。

draggable="true"

dialogタグにこれを付け忘れると、ドラッグはできるもののマウスでポイントが一発で上手く行かないダイアログになってしまいます。

かなり操作がストレスになります。


 JS

{
const dialogs = document.getElementsByTagName('dialog');
dialogs.forEach(dialog => {
let mouse = {
x: 0,
y: 0,
};
let pos = {
x: 0,
y: 0,
};
dialog.addEventListener('dragstart', evt => {
mouse.y = evt.pageY;
mouse.x = evt.pageX;
if(! dialog.style.top){
dialog.style.top = "0px";
dialog.style.left = "0px";
pos.y = 0;
pos.x = 0;
}else{
pos.y = parseInt(dialog.style.top.slice(0, -2), 10);
pos.x = parseInt(dialog.style.left.slice(0, -2), 10);
}
evt.dataTransfer.setDragImage(document.createElement('div'), 0, 0);
console.log('dragstart', evt.pageX, evt.pageY, mouse, pos, dialog.style.top);
});
dialog.addEventListener('drag', evt => {
if (evt.x === 0 && evt.y === 0) return;
const movey = (evt.pageY - mouse.y) * 2;
const movex = (evt.pageX - mouse.x) * 2;
dialog.style.top = (pos.y + movey) + 'px';
dialog.style.left = (pos.x + movex) + 'px';
console.log('drag', evt.pageX, evt.pageY, mouse, pos, dialog.style.top, movey);
});
});
}

drag開始とdrag中のマウス位置の差分をとって、drag開始時のDialog位置に足し合わせた新しい座標をセットしています。

mouseの移動量をそのまま差分で足していくと、mousemoveイベントの発行具合でだんだんズレていくので(このコードでは検証していませんが恐らく経験的に)、わざわざ初期値を保存して差分を計算しています。

 

TODO として、移動量に2を掛けないと上手く行かない理由は不明です。なのでこのコードは結果的に動いていますが何か間違っている気がします。


 最初は、

dialog.style.top = "0px";
dialog.style.left = "0px";

 がundefinedで存在しないので、セットしてしまいます。

初期位置はtop,left共に0pxらしいです。

2度め以降の移動では、dialogの初期位置もここから取るようにします。

 

 

本ドラッグ可能dialogコードは一応、手元の環境の中で

- 同ページ内に複数ダイアログで問題なし。 

- 開き直したダイアログが同じ位置に出る。

- 1度ドラッグして、再度ドラッグしてもズレたり問題は起こらない。

 ことを確認しています。

HTML Dialog上で入力中にEnterキー押下しても大丈夫にする

 HTML Dialog上で入力中にEnterキー押下してしまうとユーザ入力内容が消えてしまう問題を解決する方法です。

 HTML Dialog上でEnterキー押下した場合のデフォルト動作は、Cancelボタン押下された扱いで閉じることです。

しかしこの動作はinput elementにフォーカスが入っている状態でも有効です。

Dialog上でinput elementで入力している途中で閉じてしまいます。

しかも、WebアプリとしてはCancelボタン押下を検出したら入力をなかったことにするはずです。

結果、ユーザとしては入力途中の内容が突然消えてしまうわけで、困ります。

 

そこでこの、キャンセルで閉じてしまうDialogの動作を変更します。


// MEMO デフォルトではdialog内のinputでEnter押下すると、dialogが閉じてしまう。
// そのためEnterキーをキャンセルしつつ、入力の確定は行う処理をここで追加。
dialog.addEventListener('keydown', (event) => {
const elem = document.activeElement;
console.log(`dialog keydown "${event.key}"`, elem)
if('INPUT' === elem.tagName && 'Enter' === event.key){
elem.dispatchEvent( new KeyboardEvent('change'));
event.preventDefault();
return;
}
});


入力確定したらOKボタン押下扱いでDialogを閉じる、などしたければさらに工夫が必要になりますが、とりあえずこれでユーザ入力が失われる問題は解決します。


なお他の方法。

https://developer.hatenastaff.com/entry/2021/12/01/100000

 

 

画像Buttonのclickイベントが発火しないとき

画像Buttonのクリックイベントが発火せず、けっこう悩みました。
 



 デベロッパーツールのコンソールには特にエラーなど表示されず。

またクリックイベントも登録された状態になっています。登録忘れや削除されてしまったなどではないようです。

 


 

問題の起こったボタンがjsで生成した直後のもので、他のところではすでに同じような画像Buttonで問題なくclickイベントが動作していました。

状況的に「documentツリー内にappendしてからでないとダメなのか?」などと疑ったり試したりと悩んだものの、原因はそれではなく。

理由は全く別のところにありました。

 そもそもHTMLで「画像ボタン」 を実装する方法はいくつも種類があります。

img要素のclickイベントをListenするとか。

その中で今回はSVGを使う都合から、「Button要素にObject要素を入れて、Object要素でSVGを表示」という方法を取っていました。

 つまりこれです。

<button class="layer_is-visible">
<object data="resource/layer/is-visible-open.svg" type="image/svg+xml"></object>
</button>

 

さんざんListenの方法やタイミング、ターゲットを変えてと一通り試してから、そういえばかなり初期に画像ButtonにCSS設定していたな、 と思い出して確認したところそこに正解がありました。


CSSにて以下を記載。

/* click event Buttonのclickイベントを動作させる */
button object{
pointer-events: none;
}

 以上です。

 

恐らくButtonのchildrenにObjectがあることで、ClickイベントをObjectが受け取り下のButtonまでたどり着かないということなのだと思います。

 

CSS設定を忘れたまま別の箇所で意図しない影響が出ないよう、CSSセレクタでターゲットを絞っていたのが裏目に出ました。今度こそドキュメント全体に影響するよう変更したので、もう次からはドキュメントのどこでSVG画像ボタンを使っても大丈夫です。

...恐らく。

 

 ここでひとつ罠なのは、「内側のObject要素がClickイベントを受け止めているからButtonにClickが発生しない」にもかかわらず、「ならばとObject要素でClickイベントをListenerしても、これもまた発火しない」という部分。

(Object要素の中のSVGが要素扱いでClickイベントを受けているのかもしれないけれど、だったらデベロッパーツールでObject要素内のSVG まで展開されるのが一貫性というものでは? などと思ったり。)

 

なんというか「こういう設計(挙動)になっているきっと合理的な理由があるのだろうけれど、そんなのユーザにはわかりっこないよ!(要約)」というやつの気がします。

DOMイベント構造は複雑怪奇。

BlenderでBooleanModifierによる断面に色を付ける

前提: 

Blenderでモデルを作成する際、再加工のしやすさからBooleanModifieで形をつくる箇所がある。

例としては、銃眼の穴やモールドを開けたり、複数オブジェクトからなる給気口で同じ断面を作ったりする。

形状を作ったあと、着色する。

Blenderの着色では、オブジェクト上を平面ごとに色を塗り分けることができる。

 

問題:

しかしこのとき、BooleanModifierで作られた面は、元Objectには存在しないことになるため、Modifiyerによる加工を非可逆にApplyしてしまわなければ着色できないことになる。


解決方法:

Booleanで抜きに使うObjectと、抜く対象のObjectの両方のMaterialProperty(使用する色)に、付けたい色を登録する。

抜きに使うObjectを付けたい色に着色すると、BooleanModifierで抜いた箇所が着色される。

 

注意点として、このとき、抜く対象の側のObjectに抜き色を登録しておくのを忘れると、着色されない。


実際の作業のようす

 

着色前の状態

銃眼あたりを見ると、影が入っている

画像は結果がわかりやすいよう機体の塗装色を白に寄せています。


実際の作業の動画




加湿器をYAMAZEN KS-F407(WH) に買い替え

 YAMAZENの加熱式加湿器 KS-F407(WH) を買いました。

超音波式は家電へのカルキ集積と衛生管理の問題、あと部屋を温めるのにも都合が良いことから、熱加湿器を選んでいます。

当時2000円台の小さな加湿器で5年近くやってきていたのですが、そろそろ水漏れなどしてきたので買い替えました。

 


箱を開けるとトリセツとフィルターがついていました。

フィルターは交換用含めて2枚入っています。

フィルタの使用可能期間は使ってみた結果として、だいたい1冬くらいでしょうか。

水蒸気量の少ない設定で日中から夜中まで1日中ずっと使っている使い方の場合です。

フィルタは最初発泡スチロールかと思ったのですが、フェルトのような生地のカタマリで、最後にはボロリと千切れて使えなくなります。


本体を取り出したところ。
本体の下面はこんな感じになっています。
フタのウラはこんな感じになっています。


蒸気の出る部分は煙突のようになっています。


中に入っている付属品と説明書。

謎の黒いリングは水受け部分を囲う溝に差し込むもので、この溝を水が通っていくようになっています。

理由は不明。



タンク蓋部分。

煙突部分と水を通す部分のリングが外れます。

使用するとリングと煙突の下部にぐるりとカルキが付くので、こそげ落として掃除する感じになります。(ここにつくカルキはわりと取れやすいです。)

水蒸気をつくる底の熱源部分。

ここのカルキはとろうと思って取れる感じではありませんが、頭頂部分に溜まったカルキがたまにべろりと剥がれてきます。

水を受ける部分と熱源までの間に挟まる謎のプラスチック小物。

差し込む上下と前後の方向は決まっているのですが、片側が曲面でそれを目安に差し込もうとして、いつも逆に刺してしまいます。


 

TIGORA(ティゴラ)のトレッキング シューズ

 TIGORA(ティゴラ)の トレッキング シューズを買いました。 メインの靴がアシックスウォーキングで街歩き用なのですが、これまではこれで高尾山などの軽い山も登っていました。 今回、靴底があまりに摩耗したこともあってアシックスウォーキングを買い替えたのですが、ついでに消耗が激...