独自の拡張および縮小コンテンツパネルを作成する
公開: 2022-03-10これまでは「開閉パネル」と呼んでいましたが、拡張パネル、またはより簡単に拡張パネルとも呼ばれます。
私たちが話していることを正確に明確にするために、CodePenのこの例に進んでください:
これが、この短いチュートリアルで作成するものです。
機能の観点から、私たちが探しているアニメーションの開閉を実現する方法はいくつかあります。 それぞれのアプローチには、独自の利点とトレードオフがあります。 この記事では、「go-to」メソッドの詳細を共有します。 まず、考えられるアプローチを考えてみましょう。
アプローチ
これらの手法にはさまざまなバリエーションがありますが、大まかに言えば、アプローチは次の3つのカテゴリのいずれかに分類されます。
- コンテンツの
height
またはmax-height
さをアニメーション化/遷移します。 -
transform: translateY
を使用して要素を新しい位置に移動し、パネルが閉じているように見せて、要素が終了位置にある状態で変換が完了したら、DOMを再レンダリングします。 - 1または2の組み合わせ/バリエーションを実行するライブラリを使用してください。
各アプローチの考慮事項
パフォーマンスの観点から、変換を使用する方が、高さ/最大高さをアニメーション化または遷移するよりも効果的です。 変換を使用すると、移動する要素がラスタライズされ、GPUによってシフトされます。 これはGPUにとって安価で簡単な操作であるため、パフォーマンスが大幅に向上する傾向があります。
変換アプローチを使用する場合の基本的な手順は次のとおりです。
- 折りたたむコンテンツの高さを取得します。
- 折りたたむコンテンツの高さだけ、
transform: translateY(Xpx)
を使用して、コンテンツとその後のすべてを移動します。 選択したトランジションを使用して変換を操作し、心地よい視覚効果を提供します。 - JavaScriptを使用して
transitionend
イベントをリッスンします。 起動したら、次のようにdisplay: none
、変換を削除すると、すべてが適切な場所に配置されます。
悪くないですね。
ただし、この手法にはいくつかの考慮事項があるため、パフォーマンスが絶対的に重要でない限り、カジュアルな実装では避ける傾向があります。
たとえば、 transform: translateY
アプローチでは、要素のz-index
を考慮する必要があります。 デフォルトでは、上に変換される要素はDOMのトリガー要素の後にあるため、上に変換されると、それらの前にあるものの上に表示されます。
また、DOMで折りたたむコンテンツの後に表示されるものの数も考慮する必要があります。 レイアウトに大きな穴を開けたくない場合は、JavaScriptを使用して、移動するすべてのものをコンテナー要素にラップし、それを移動する方が簡単な場合があります。 管理しやすいですが、さらに複雑になりました。 ただし、これは、In / Outでプレーヤーを上下に動かすときに行ったアプローチの一種です。 ここでそれがどのように行われたかを見ることができます。
よりカジュアルなニーズのために、私はコンテンツのmax-height
を移行する傾向があります。 このアプローチは、変換ほどには機能しません。 その理由は、ブラウザがトランジション全体で折りたたみ要素の高さをトゥイーンしているためです。 これにより、ホストコンピュータほど安価ではない多くのレイアウト計算が発生します。
ただし、このアプローチは単純さの観点からは勝ちます。 前述の計算上のヒットに苦しむことの見返りは、DOMリフローがすべての位置とジオメトリを処理することです。 計算方法はほとんどなく、それをうまく実行するために必要なJavaScriptは比較的単純です。
部屋の中の象:詳細と要約要素
HTMLの要素についての深い知識を持っている人は、 details
とsummary
要素の形でこの問題に対するネイティブHTMLソリューションがあることを知っているでしょう。 マークアップの例を次に示します。
<details> <summary>Click to open/close</summary> Here is the content that is revealed when clicking the summary... </details>
デフォルトでは、ブラウザはsummary要素の横に小さな開示三角形を提供します。 概要をクリックすると、概要の下の内容が表示されます。
いいね? 詳細はJavaScriptのtoggle
イベントもサポートしているので、この種のことを実行して、開いているか閉じているかに基づいてさまざまなことを実行できます(この種のJavaScript式が奇妙に見える場合でも心配しないでください。これについては、後で詳しく説明します。詳細はまもなく):
details.addEventListener("toggle", () => { details.open ? thisCoolThing() : thisOtherThing(); })
OK、私はあなたの興奮をそこで止めます。 詳細と要約要素はアニメーション化されません。 デフォルトではなく、現在、追加のCSSとJavaScriptを使用してアニメーション/トランジションを開いたり閉じたりすることはできません。
あなたが他のことを知っているなら、私は間違っていると証明されたいです。
悲しいことに、開閉の美学が必要なので、袖をまくり上げて、他のツールを自由に使ってできる限り最善で最もアクセスしやすい仕事をしなければなりません。
そうです、気のめいるようなニュースが邪魔にならないように、このことを実現することに取り掛かりましょう。
マークアップパターン
基本的なマークアップは次のようになります。
<div class="container"> <button type="button" class="trigger">Show/Hide content</button> <div class="content"> All the content here </div> </div>
エキスパンダーをラップするための外部コンテナーがあり、最初の要素はアクションのトリガーとして機能するボタンです。 ボタンのtype属性に注目してください。 デフォルトではフォーム内のボタンが送信を実行するので、私は常にそれを含めます。 フォームが機能せず、ボタンがフォームに含まれている理由を疑問に思って数時間を無駄にしていることに気付いた場合。 type属性を確認してください!
ボタンの次の要素はコンテンツドロワー自体です。 隠したり見せたりしたいものすべて。
物事を生き生きとさせるために、CSSカスタムプロパティ、CSSトランジション、および小さなJavaScriptを利用します。
基本ロジック
基本的なロジックは次のとおりです。
- ページを読み込んで、コンテンツの高さを測定します。
- CSSカスタムプロパティの値として、コンテナ上のコンテンツの高さを設定します。
-
aria-hidden: "true"
属性を追加して、コンテンツをすぐに非表示にします。aria-hidden
を使用すると、支援技術がコンテンツも非表示になっていることを認識できます。 - コンテンツクラスの
max-height
がカスタムプロパティの値になるようにCSSを接続します。 - トリガーボタンを押すと、aria-hiddenプロパティがtrueからfalseに切り替わり、コンテンツの
max-height
が0
とカスタムプロパティで設定された高さの間で切り替わります。 そのプロパティの遷移は、視覚的なセンスを提供します—好みに合わせて調整してください!
注:これは、 max-height: auto
がコンテンツの高さと等しい場合にクラスまたは属性を切り替える単純なケースになります。 悲しいことに、そうではありません。 行って、ここでW3Cにそれについて叫んでください。
そのアプローチがコードでどのように現れるかを見てみましょう。 番号付きのコメントは、コード内の上記と同等の論理ステップを示しています。
JavaScriptは次のとおりです。
// Get the containing element const container = document.querySelector(".container"); // Get content const content = document.querySelector(".content"); // 1. Get height of content you want to show/hide const heightOfContent = content.getBoundingClientRect().height; // Get the trigger element const btn = document.querySelector(".trigger"); // 2. Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true"); }, 0); btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true"); })
CSS:
.content { transition: max-height 0.2s; overflow: hidden; } .content[aria-hidden="true"] { max-height: 0; } // 4. Set height to value of custom property .content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px); }
注意点
複数の引き出しはどうですか?
ページに多数の開閉式引き出しがある場合、サイズが異なる可能性があるため、それらすべてをループする必要があります。
これを処理するには、 querySelectorAll
を実行してすべてのコンテナーを取得してから、 forEach
内の各コンテンツのカスタム変数の設定を再実行する必要があります。
そのsetTimeout
コンテナを非表示に設定する前に、期間が0
のsetTimeout
があります。 これは間違いなく不要ですが、コンテンツの高さを読み取れるようにページが最初にレンダリングされるようにするための「ベルトとブレース」アプローチとして使用します。
ページの準備ができたときにのみこれを起動します
他のことが起こっている場合は、ページの読み込み時に初期化される関数でドロワーコードをラップすることを選択できます。 たとえば、ドロワー関数がinitDrawers
という関数にラップされているとすると、次のように実行できます。
window.addEventListener("load", initDrawers);
実際、これはまもなく追加されます。
追加データ-*コンテナの属性
外側のコンテナにもデータ属性があり、これも切り替えられます。 これは、引き出しが開閉するときにトリガーまたはコンテナーで変更する必要があるものがある場合に追加されます。 たとえば、何かの色を変更したり、アイコンを表示または切り替えたりしたい場合があります。
カスタムプロパティのデフォルト値
CSSのカスタムプロパティには1000px
のデフォルト値が設定されています。 これは、値内のコンマの後のビットです: var(--containerHeight, 1000px)
。 これは、 --containerHeight
が何らかの方法で台無しになった場合でも、適切な遷移が必要であることを意味します。 明らかに、ユースケースに適したものに設定できます。
デフォルト値の100000pxを使用しないのはなぜですか?
max-height: auto
が遷移しないことを考えると、なぜ必要以上の値の設定された高さを選択しないのか疑問に思われるかもしれません。 たとえば、10000000px?
そのアプローチの問題は、常にその高さから移行することです。 トランジション期間が1秒に設定されている場合、トランジションは1秒で10000000px移動します。 コンテンツの高さが50ピクセルしかない場合は、非常にすばやく開閉効果が得られます。
トグルの三項演算子
属性を切り替えるために、三項演算子を数回使用しました。 一部の人々はそれらを嫌いますが、私や他の人々はそれらを愛しています。 最初は少し奇妙で少し「コードゴルフ」のように見えるかもしれませんが、構文に慣れると、標準のif / elseよりも簡単に読み取れると思います。
初心者の場合、三項演算子はif / elseの要約形式です。 チェックするものが最初、次に?
チェックがtrueの場合に実行する内容を区切り、次に:
を区切り、チェックがfalseの場合に実行する内容を区別します。
isThisTrue ? doYesCode() : doNoCode();
属性の切り替えは、属性が"true"
に設定されているかどうかを確認し、設定されている場合は"false"
に設定し、そうでない場合は"true"
に設定します。
ページのサイズ変更はどうなりますか?
ユーザーがブラウザウィンドウのサイズを変更すると、コンテンツの高さが変わる可能性が高くなります。 したがって、そのシナリオでコンテナの高さを設定して再実行することをお勧めします。 現在、このような事態を検討しているので、少しリファクタリングするのに良い時期のようです。
高さを設定する関数と、相互作用を処理する関数を作成できます。 次に、ウィンドウに2つのリスナーを追加します。 1つは前述のようにドキュメントが読み込まれるとき用で、もう1つはサイズ変更イベントをリッスンするためのものです。
少し余分なA11Y
aria-expanded
、 aria-controls
、およびaria-labelledby
属性を利用することで、アクセシビリティについて少し余分な考慮事項を追加することができます。 これにより、引き出しが開いたり拡張されたりしたときに、支援技術をより適切に示すことができます。 aria-controls="IDofcontent"
と一緒にボタンマークアップにaria-expanded="false"
を追加します。ここで、 IDofcontent
は、コンテンツコンテナに追加するIDの値です。
次に、別の三項演算子を使用して、JavaScriptのクリック時にaria-expanded
属性を切り替えます。
すべて一緒に
ページの読み込み、複数のドロワー、追加のA11Y作業、サイズ変更イベントの処理により、JavaScriptコードは次のようになります。
var containers; function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights); } window.addEventListener("load", initDrawers); function setHeights() { containers.forEach(container => { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e => { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); }); } function wireUpTriggers() { containers.forEach(container => { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () => { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); }); }
こちらのCodePenで遊ぶこともできます:
概要
しばらくの間、さらに洗練され、ますます多くの状況に対応することが可能ですが、コンテンツ用の信頼性の高い開閉ドロワーを作成する基本的な仕組みは、今やあなたの手の届くところにあるはずです。 うまくいけば、あなたはいくつかの危険にも気づいています。 details
要素をアニメーション化することはできませんmax-height: auto
は期待どおりに機能しません。大規模なmax-height値を確実に追加して、すべてのコンテンツパネルが期待どおりに開くことを期待することはできません。
ここでこのアプローチを繰り返すには、コンテナを測定し、その高さをCSSカスタムプロパティとして保存し、コンテンツを非表示にしてから、単純なトグルを使用して、 max-height
0とカスタムプロパティに保存した高さを切り替えます。
これは絶対的に最高のパフォーマンスを発揮する方法ではないかもしれませんが、ほとんどの状況で完全に適切であり、実装が比較的簡単であるというメリットがあります。