React x tailwindcssをベースにしたUIコンポーネントであるheadlessUIのDialog(Modal)コンポーネントを使って開発しています。Modalが開くと背景のスクロールを防ぐためにhtmlタグにoverflow: hidden; padding-right: 15px;
が付与されます(padding-rightは消えたスクロールバーのズレを防ぐため)。
しかしfixedで上部固定しているヘッダーが中央揃え(mx-auto
)のため、このpadding-right
により中央位置がずれ、中の要素が右にずれます。
margin-leftを固定値にしてしまえば影響をうけないのですが、なるべく画面幅によらず綺麗に中央揃えにしたく。。良い解決方法はありませんでしょうか‥?
コード
tsx
1// Layout.tsx 2import { Popover, Transition } from '@headlessui/react'; 3import { InformationCircleIcon, MenuIcon, XIcon } from '@heroicons/react/outline'; 4import Image from 'next/image'; 5import Link from 'next/link'; 6import { Fragment, useState } from 'react'; 7 8export default function Layout() => { 9 const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); 10 11 function openLoginModal() { 12 setIsLoginModalOpen(true) 13 } 14 15 return ( 16 <Popover className='fixed left-0 right-0 bg-white z-10'> <!-- ここがfiexdでwindow基準なので --> 17 <div className='mx-auto max-w-7xl px-2 lg:px-4'> <!-- このmx-autoによる中心位置がずれる --> 18 <div className='flex flex-col lg:flex-row lg:justify-end lg:items-center lg:h-24 lg:space-x-10'> 19 <div className="flex justify-end lg:justify-start items-center h-14"> 20 <div className='flex flex-grow'> 21 <Link href='/' passHref> 22 <a className='flex items-center'> 23 <Image 24 className='w-auto h-8 sm:h-10 cursor-pointer' 25 src={logo} 26 alt='' 27 width='164' 28 height='48' 29 /> 30 </a> 31 </Link> 32 </div> 33 </div> 34 35 <div className="flex flex-row items-center justify-end space-x-10"> 36 <Popover.Group as='nav' className='hidden lg:flex space-x-10'> 37 <a 38 href='https://example.com/about' 39 className='text-base font-medium text-gray-500 hover:text-gray-900' 40 > 41 About 42 </a> 43 </Popover.Group> 44 45 <div className='hidden lg:flex lg:flex-1 items-center'> 46 <LoginModal isOpen={isLoginModalOpen} setIsOpen={setIsLoginModalOpen}> 47 <button 48 className='mx-auto justify-center py-2 px-4 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-md border border-transparent' 49 onClick={openLoginModal} 50 > 51 Sign in with Twitter 52 </button> 53 </LoginModal> 54 </div> 55 </div> 56 </div> 57 </div> 58 </Popover> 59 ) 60 }
LoginModal.tsxの中身はほぼそのままで、htmlにoverflow, padding-rightが付与されるのはheadlessuiの仕様です。
tsx
1// LoginModal.tsx 2import { Dialog, Transition } from '@headlessui/react' 3import { Fragment } from 'react' 4import TwitterSigninButton from './TwitterSigninButton' 5 6export default function LoginModal({ isOpen, setIsOpen, children }) { 7 8 return ( 9 <> 10 {children} 11 <Transition.Root show={isOpen} as={Fragment}> 12 <Dialog 13 as="div" 14 auto-reopen="true" 15 className="fixed z-50 inset-0 overflow-y-auto" 16 onClose={setIsOpen} 17 > 18 <div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> 19 <Transition.Child 20 as={Fragment} 21 enter="ease-out duration-300" 22 enterFrom="opacity-0" 23 enterTo="opacity-100" 24 leave="ease-in duration-200" 25 leaveFrom="opacity-100" 26 leaveTo="opacity-0" 27 > 28 <Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> 29 </Transition.Child> 30 31 {/* This element is to trick the browser into centering the modal contents. */} 32 <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"> 33 ​ 34 </span> 35 <Transition.Child 36 as={Fragment} 37 enter="ease-out duration-300" 38 enterFrom="opacity-0 scale-95" 39 enterTo="opacity-100 scale-100" 40 leave="ease-in duration-200" 41 leaveFrom="opacity-100 scale-100" 42 leaveTo="opacity-0 scale-95" 43 > 44 <div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl"> 45 <Dialog.Title 46 as="h3" 47 className="text-lg font-medium leading-6 text-gray-900 mb-3" 48 > 49 アカウント登録・ログイン 50 </Dialog.Title> 51 <div className="mt-2"> 52 <p className="text-sm text-gray-500 mb-4"> 53 アカウント登録、もしくはログインすると<a href="https://example.com/terms" target="_blank" rel="noreferrer" className="text-blue-400">利用規約</a>、<a href="https://example.com/privacy" target="_blank" rel="noreferrer" className="text-blue-400">プライバシーポリシー</a>に同意したものとみなされます。 54 </p> 55 <TwitterSigninButton /> 56 </div> 57 </div> 58 </Transition.Child> 59 </div> 60 </Dialog> 61 </Transition.Root> 62 </> 63 ) 64}
環境
"next": "11.0.1", "react": "17.0.2", "@headlessui/react": "^1.4.0", "tailwindcss": "^2.2.7",
試したこと
###htmlにoverflow: scroll; !important
を追加
常にスクロールバーを表示する方法ですが、fixed部分は確かに解消するものの、Modalの背面スクロールが可能になってしまうので本末転倒なのと、今度は今まで大丈夫だったfixed以外の要素がpadding-right: 15px;
により左にずれます。
isLoginModalOpenによりcssクラスを出し分け
css
1.scrollbar-padding { 2 padding-right: 15px; 3}
tsx
1 <Popover className={isLoginModalOpen 2 ? 'fixed left-0 right-0 bg-white z-10 scrollbar-padding' 3 : 'fixed left-0 right-0 bg-white z-10'}>
このようにしてModalが出た時だけfixed要素に独自クラスを追加する方法。うまくいく場合はある(fixedの中の要素が微動だにしない)ものの、基本的にはスクロールバーの表示やhtmlへのpadding-right
付与のタイミングとずれてより左右にガクガクなります。
isScrollbarShownステートを使って判定
上記を応用してスクロールバーの有無を判定してやろうと思いましたが、下記のコードでは常にfalseになり上手く行かず、、また中央揃えのためにわざわざ不安定なstateを増やすのもナンセンスと思い途中でやめました。。
tsx
1const [isScrollbarShown, setIsScrollbarShown] = useState(null); 2 3useEffect(() => { 4 if( window.innerHeight >= document.documentElement.clientHeight ){ 5 //スクロールバー無し 6 setIsScrollbarShown(false); 7 } else { 8 setIsScrollbarShown(true); 9}, [isLoginModal])
調べたこと
headlessui github issue
https://github.com/tailwindlabs/headlessui/search?q=padding-right&type=issues
解決策はありませんでした。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。