hogashi.*

日記から何から

position: stickyで貼り付いたときだけ境界線を出す(JavaScriptを使わない)

貼り付く見出し

貼り付く見出し

 スクロールしてこの見出しが画面上部に貼り付いたときだけ、見出しの下に境界線を出したい。

 技として、境界線用の要素をいっこ用意して、それを見出しの裏に忍ばせておく方法がある。見出しが画面上部に貼り付いたときに、下に境界線が出てくる。

画面上部に貼り付いたときに境界線が下に出る

貼り付く見出し

貼り付く見出し

 どうなってるかというと、背景色を透明にするとこう。

  • 見出しの要素に margin-top: -1px; をつけることで、境界線の要素を覆い隠して見えなくしておく
  • sticky の top の値を、見出しの縦幅 + 1px 分にして、 1px 下に貼り付くようにする
  • 境界線が躍り出る空間を得るために、外側の包む要素の縦幅は、見出しの縦幅 + 1px 分にしておく

 むずいけど、これで、見出しが貼り付いたときに、境界線がちょうど背景色のすぐ下に躍り出る。

こういう感じで、背景色の裏で境界線が動いている
<div class="sticky-with-border">
  <h3 class="border" inert>貼り付く見出し</h3>
  <h3 class="title">貼り付く見出し</h3>
</div>

<style>
.sticky-with-border {
  position: sticky;
  top: 10px;
  margin: 33px 0;
  height: 51px;
}
.entry .entry-inner .sticky-with-border h3.border {
  position: sticky;
  top: 61px;
  margin: 0;
  padding: 0 10px;
  width: fit-content;
  height: 1px;
  background-color: black;
  color: transparent;
}
.entry .entry-inner .sticky-with-border h3.title {
  position: sticky;
  top: 0;
  margin: -1px 0 0;
  padding: 0 10px;
  display: flex;
  flex-flow: column;
  justify-content: center;
  width: fit-content;
  height: 50px;
  background-color: #eee;
}
</style>

 境界線の要素を h3 にしてるのは、横幅を合わせるため (なので文字も同じものを入れつつ文字色を透明にしている)。 width: 100% とかにしたら、空の div とかでも良い。

 ちなみに、他の技として animation-timeline: scroll() とかはあって % 指定で border を出したりはできるけど、張り付いたらすぐに、というのは % の数字を調整しないといけなくてむずい。 scroll() - CSS: カスケーディングスタイルシート | MDN

 追記(2024/1/17): id:susisu から inert 属性をつけておくとよさそうと指摘をもらったのでつけてあります(感謝) HTMLElement: inert プロパティ - Web API | MDN。最初は落書きのつもりで雑に書いてたのであんまり気にしてなかったけど、そういえば属性つけるだけで重複回避できるのだった、と思い出せて助かった。

 追記(2024/1/17): 擬似クラスとかないんだろうか……とか思っていたけど、 :stuck という提案はあったっぽいけど設計規則とそぐわないので却下されてそうと id:mizdra に教えてもらった(感謝) [css-selectors] :stuck pseudo-class feature suggestion · Issue #1656 · w3c/csswg-drafts · GitHubCSS で変えられるものを擬似クラスにしてしまうと無限ループになりうるのはたしかにで面白い。あと position: sticky もうちょっとなんとかならないかという議論自体は続いていて、その中に今回の記事のような場合も議題としてあるとのことだった [css-position] Meta-issue: Unresolved `sticky` positioning use cases · Issue #11145 · w3c/csswg-drafts · GitHub

Redashではクエリ結果にHTMLを使えるので便利 長いカラムをdetailsで畳める ほか

 Redash (https://redash.io/) でデータをクエリしたとき、長い値が入ったカラムがあると、クエリ結果が縦に長くなってしまいがち (画像では 3行しかないけど、 100行とかあると見づらいことも多い)。

長い値があるクエリ結果 / 縦に長くなりがちで見づらいこともある

 Redash では、なんとクエリ結果に HTML が書かれているとレンダされるので、 details タグで囲うことで畳むことができる。便利。*1

長い値をdetailsタグで畳むことができる / クリックしたら全部見れる

 リンクを張ることもできる。管理画面の URL などで、クエリ結果の id をクエリパラメータに入れたり、名前や時刻をリンクテキストにしたりすると便利。おもてなしとして target="_blank" をつければ、新しいタブで開くことができる。

リンクを張ることができる / target="_blank" のような指定もできる

 input や textarea で値をコピーしやすくできて便利。必要に応じて readonly などをつけたりするのもよさそう。

inputタグで好きなテキストをコピーしやすくできる
改行を含む文字列ならtextareaがコピーしやすい

 CSS も (タグの style 属性で) 書けるので、工夫次第では視覚的にわかりやすい表をつくることができると思います (上にあげたものが見やすいかはさておき……) *2。逆にごちゃごちゃにならないように気をつけつつ、便利に使っていきたいですね。

あけましておめでとうございます~〜〜

 はてなエンジニア Advent Calendar 2024 - Hatena Developer Blog の 35日目の記事でした。

*1:これはつまり、カラムの値として HTML が入っていると、素朴に select するだけでレンダされて出てきてしまうということなので、扱いには注意……

*2:JavaScript は書けない

GitHub issueで箇条書きのハイフンなしで改行するにはShift+Enter

 GitHub issue で、 Shift + Enter で、箇条書きのハイフンなしで改行ができる。続けて何行でもできる。

Shift + Enter で改行している様子
- コードの様子
  - before  <-- ここで Shift + Enter すると
  |         <-- ここにハイフンなしで改行される

 ショートカットは Keyboard shortcuts - GitHub Docs に一覧があるけど、 Shift + Enter は載ってない……。

querySelectorAllの結果をmapしたいときはArray.fromすると良い

 こんにちは〜 はてなエンジニア - Qiita Advent Calendar 2024 - Qiita の 1日目です。

querySelectorAllの結果をmapしたいとき

 JavaScript で、 Document: querySelectorAll() メソッド の結果を map したいことがある。が、そのままだと map できない。
 Chrome の devtools で試すとこう↓。 map は関数ではないです、というエラーになっている。

document.querySelectorAll('div').map(div => div.innerText)
// Uncaught TypeError: document.querySelectorAll(...).map is not a function
//    at <anonymous>:1:34
querySelectorAllの結果をそのままmapしようとしてエラーになっている様子

 map するには、 Array.from() を使って、 Array インスタンスにしてあげると良い。

Array.from(document.querySelectorAll('div')).map(div => div.innerText)
//=> ['hoge', ...] (innerText が入った配列が返ってくる)
querySelectorAllの結果をArray.fromしたのでmapできる様子

くわしく

 querySelectorAll の戻り値は NodeList というオブジェクト。

DocumentquerySelectorAll() メソッドは、指定された CSS セレクターに一致する文書中の要素のリストを示す静的な(生きていない)NodeList を返します。

https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll

 NodeList に map メソッドが存在しないので、そのまま map しようとすると冒頭のエラーになる。 MDN の NodeList のページには、まさに Array.from を使うといい、と書かれている。

メモ: NodeListArray とは異なりますが、forEach() メソッドで処理を反復適用することは可能です。Array.from() を使うことで Array に変換することができます。

https://developer.mozilla.org/ja/docs/Web/API/NodeList
ちなみに"静的な(生きていない)NodeList"とは

 生きた NodeList と生きていない NodeList というのは、 DOM の変化に応じて、自動的に更新されるかどうか、という話題。 NodeList - Web API | MDN に詳しい。
 querySelectorAll の戻り値は生きていないほうなので、取得して変数に格納したオブジェクトの内容が変化しない。逆に、 Node: childNodes プロパティ は生きた NodeList なので、 DOM が変化すると、その内容も変わりうる。

 試しに、 body タグの childNodes を変数に格納してから、 body タグに div タグをひとつ増やしてみた。変数に格納しておいたオブジェクトが変化していることがわかる。

child = document.body.childNodes
//=> NodeList(5) [script, div#root, script#hydration, div#a11y-status-message, div.ReactModalPortal]
document.body.insertAdjacentHTML('beforeend', '<div>hello!</div>')
//=> undefined
child
//=> NodeList(6) [script, div#root, script#hydration, div#a11y-status-message, div.ReactModalPortal, div]
childNodes(を変数に格納したオブジェクト)が変化している様子

 querySelectorAll に似たメソッド群で Document: getElementsByClassName() メソッド などは、生きた HTMLCollection が戻り値になっている。 querySelectorAll と同じ気持ちでコードを書いているとハマることがありそう……。

list = document.getElementsByClassName('browsers')
//=> HTMLCollection [div.browsers]
document.body.insertAdjacentHTML('beforeend', '<div class="browsers">Hi!</div>')
//=> undefined
list
//=> HTMLCollection(2) [div.browsers, div.browsers]
getElementsByClassNameの結果(を変数に格納したオブジェクト)が変化している様子

拡張機能「twitter画像原寸ボタン」v7.1.0公開

 拡張機能twitter画像原寸ボタン」v7.1.0 を公開しました。主に依存パッケージとパッケージマネージャの更新のみで、機能に変化はありません。細かいところでは、ポップアップの設定画面の保存ボタンのテキストが折り返すことがあったので広めにしました。

 インストールはこちら:

Google Chrome
chromewebstore.google.com

Microsoft Edge
microsoftedge.microsoft.com