新しい歴史

カスタム ドロップダウン要素の適切な表示: ハウツー ガイド

Andrei Sieedugin6m2025/04/04
Read on Terminal Reader

長すぎる; 読むには

あなたのダウンロードアイテムをカットすることなくどこにでも表示するのに役立つ小さなトリック。
featured image - カスタム ドロップダウン要素の適切な表示: ハウツー ガイド
Andrei Sieedugin HackerNoon profile picture
0-item

デフォルトの HTML コンポーネントを改良するのではなく、独自のコンポーネントを作成するときの気持ちをご存知ですか? デザイン チームが美しいものを作成したものの、ブラウザーがそのままではサポートしておらず、あらゆる場所で修正するのは悪夢になります。この苦痛は誰もが知っていますが、こうした課題があるからこそ私たちの仕事は面白くなります。


今日は、このスリリングな旅の途中で私たちを待ち受ける落とし穴の 1 つ、つまり、選択メニューや日付ピッカーなどのドロップダウン要素の配置についてお話ししたいと思います。

絶対に間違っている

一見、 position: absoluteすべての問題が解決するように見えますし、ある程度は解決します。しかし、その後、モーダル ウィンドウがすべてを台無しにします。



ドロップダウンがオーバーフローすると、切り取られます。もちろん、下にスクロールして表示することもできますが、デザイナーが鋭利な物であなたに届かないように祈るほうがよいでしょう。


それは完璧からは程遠いですが、もっと改善できるはずです。

'fixed'で修正する

コンテンツをすべての上に表示したい場合は、 position: fixed必要です。唯一の問題は、親要素の座標が失われることです。固定要素は本質的に非常に独立しています。つまり、次の状況では、ドロップダウン要素の正確な座標を決定するだけで済みます。


  • 表示すると。
  • 内容が変更された場合。
  • ウィンドウやスクロール可能な親をスクロールするとき。
  • ウィンドウやスクロール可能な親のサイズを変更するとき。


また、トグルが画面の下部に近すぎる場合は、トグルの上に表示するかどうかも決定する必要があります。実行できそうです。


私は Vue.js を使用しますが、React や Angular を好む場合でも簡単に理解できるはずです。

この店を盛り上げよう

使用する構造は次のとおりです。

 export const useDropdownAttributes = () => { const dropdownWidth = ref(''); const dropdownTop = ref(''); const dropdownBottom = ref(''); const dropdownLeft = ref(''); const isDirectedUpwards = ref(false); const togglerRect = ref<DOMRect>(); const dropdownRect = ref<DOMRect>(); const autodetectPosition = ( isDropdownDisplayed: Ref<boolean>, togglerElement: HTMLElement | null = null, dropdownElement: HTMLDivElement | null = null, dropdownContent: Ref<unknown> | ComputedRef<unknown> = ref([]), isUpwardPreferred = false, ) => { // ... } return { autodetectPosition, dropdownTop, dropdownBottom, dropdownLeft, dropdownWidth, isDirectedUpwards, togglerRect, dropdownRect, }; };

ドロップダウンの位置には 4 つの変数があり、さらにisDirectedUpwardsフラグとそれらすべてを更新する関数があります。また、トグルとドロップダウンの Rects 用に 2 つの変数も返します。これは、たとえば、コンテンツの中央に揃える必要があるツールチップの場合に便利です。


ご存知のとおり、スクロール可能な親のスクロールとサイズ変更も処理する必要があるため、それを見つけるための関数を作成しましょう。

 const getFirstScrollableParent = (element: HTMLElement | null): HTMLElement => { const parentElement = element?.parentElement; if (!parentElement) return document.body; const overflowY = window.getComputedStyle(parentElement).overflowY; if (overflowY === 'scroll' || overflowY === 'auto') return parentElement; return getFirstScrollableParent(parentElement); };


次に、メイン関数を追加しましょう。

 const autodetectPosition = ( isDropdownDisplayed: Ref<boolean>, togglerElement: HTMLElement | null = null, dropdownElement: HTMLElement | null = null, dropdownContent: Ref<unknown> | ComputedRef<unknown> = ref([]), isUpwardPreferred = false, ) => { if (!togglerElement || !dropdownElement) return; const updateDropdownAttributes = () => { togglerRect.value = togglerElement.getBoundingClientRect(); dropdownRect.value = dropdownElement.getBoundingClientRect(); dropdownWidth.value = `${togglerRect.value.width}px`; dropdownBottom.value = `${window.innerHeight - togglerRect.value.top}px`; dropdownTop.value = `${ window.innerHeight - togglerRect.value.bottom - dropdownRect.value.height }px`; dropdownLeft.value = `${togglerRect.value.left}px`; }; const handleResize = () => { requestAnimationFrame(updateDropdownAttributes); }; const handleScroll = () => { requestAnimationFrame(updateDropdownAttributes); }; watch( [isDropdownDisplayed, dropdownContent], ([newVal, _]) => { const scrollableParent = getFirstScrollableParent(togglerElement); if (!newVal) { window.removeEventListener('resize', handleResize); window.removeEventListener('scroll', handleScroll); scrollableParent.removeEventListener('resize', handleResize); scrollableParent.removeEventListener('scroll', handleScroll); return; } requestAnimationFrame(() => { const distanceFromBottom = window.innerHeight - togglerElement.getBoundingClientRect().bottom; const distanceFromTop = togglerElement.getBoundingClientRect().top; const dropdownHeight = dropdownElement.offsetHeight; isDirectedUpwards.value = isUpwardPreferred ? distanceFromTop > dropdownHeight : distanceFromBottom < dropdownHeight && distanceFromTop > dropdownHeight; updateDropdownAttributes(); window.addEventListener('resize', handleResize); window.addEventListener('scroll', handleScroll); scrollableParent.addEventListener('resize', handleResize); scrollableParent.addEventListener('scroll', handleScroll); }); }, { deep: true }, ); };

isDropdownDisplayeddropdownContentを渡して、それらの更新に反応できるようにします。


位置を計算するために必要なtogglerElementdropdownElementも渡します。


最後に、デフォルトでトグルの上にドロップダウンを表示したい場合に備えて、 isUpwardPreferredがあります。

リラックスして楽しむ時間

コンポーネントでは、次のようなものが必要になります (テンプレートのトグルとドロップダウンに参照を追加していると想定します)。

 const { autodetectPosition, dropdownTop, dropdownBottom, dropdownLeft, dropdownWidth, isDirectedUpwards, } = useDropdownAttributes(); const togglerRef = ref<HTMLElement>(); const dropdownRef = ref<HTMLElement>(); const isDropdownShown = ref(false); onMounted(() => { autodetectPosition(isDropdownShown, togglerRef.value?.$el, dropdownRef.value?.$el); });


CSS は次のようになります。

 .dropdown { position: fixed; bottom: v-bind('isDirectedUpwards ? dropdownBottom : dropdownTop'); left: v-bind('dropdownLeft'); width: v-bind('dropdownWidth'); min-width: 0; }


できました。ドロップダウンはオーバーフローしている場合でも適切に表示され、下に十分なスペースがない場合はトグルの上に移動します。



記事の最後なので、何か明るいことを伝えたいのですが、アイデアが思いつきません。なので、今回は「幸運を祈る」しか言えません。幸運を祈っています。👋


完全なコードはGitHubで見つかります。

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks