下一个/字体适用于除一个特定组件之外的任何地方

问题描述 投票:0回答:5

下一个/字体

将 Next.js 与 TypeScript 和 Tailwind CSS 结合使用

这是我第一次使用新的

next/font
包。我按照 Next.js 的教程进行操作,设置很简单。我同时使用 Inter 和一种名为 App Takeoff 的自定义本地字体。为了实际使用这两种字体,我使用了 Tailwind CSS,其中 Inter 连接到
font-sans
,App Takeoff 连接到
font-display

除一处外一切正常

我已经在文件之间进行了大量的测试,由于某种原因,除了我的

Modal
组件之外,两种字体都可以在任何地方工作。 (请参阅底部的有用更新,了解为什么它在
Modal
组件中不起作用。)

示例

index.tsx

modal.tsx 通过index.tsx

如您所见,当字体不在模态中时,它们可以正常工作,但一旦处于模态中,它们就不起作用。

这是一些相关代码:

// app.tsx

import '@/styles/globals.css'
import type { AppProps } from 'next/app'

import { Inter } from 'next/font/google'
const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter'
})

import localFont from 'next/font/local'
const appTakeoff = localFont({
  src: [
    {
      path: '../fonts/app-takeoff/regular.otf',
      weight: '400',
      style: 'normal'
    },
    {
      path: '../fonts/app-takeoff/regular.eot',
      weight: '400',
      style: 'normal'
    },
    {
      path: '../fonts/app-takeoff/regular.woff2',
      weight: '400',
      style: 'normal'
    },
    {
      path: '../fonts/app-takeoff/regular.woff',
      weight: '400',
      style: 'normal'
    },
    {
      path: '../fonts/app-takeoff/regular.ttf',
      weight: '400',
      style: 'normal'
    }
  ],
  variable: '--font-app-takeoff'
})

const App = ({ Component, pageProps }: AppProps) => {
  return (
    <div className={`${inter.variable} font-sans ${appTakeoff.variable}`}>
      <Component {...pageProps} />
    </div>
  )
}

export default App
// modal.tsx

import type { FunctionComponent } from 'react'
import type { Modal as ModalProps } from '@/typings/components'
import React, { useState } from 'react'
import { Fragment } from 'react'
import { Transition, Dialog } from '@headlessui/react'

const Modal: FunctionComponent<ModalProps> = ({ trigger, place = 'bottom', className, addClass, children }) => {

  const [isOpen, setIsOpen] = useState(false),
        openModal = () => setIsOpen(true),
        closeModal = () => setIsOpen(false)

  const Trigger = () => React.cloneElement(trigger, { onClick: openModal })

  const enterFrom = place === 'center'
    ? '-translate-y-[calc(50%-12rem)]'
    : 'translate-y-full sm:-translate-y-[calc(50%-12rem)]'

  const mainPosition = place === 'center'
    ? '-translate-y-1/2'
    : 'translate-y-0 sm:-translate-y-1/2'

  const leaveTo = place === 'center'
    ? '-translate-y-[calc(50%+8rem)]'
    : 'translate-y-full sm:-translate-y-[calc(50%+8rem)]'

  return (
    <>
    
      <Trigger />

      <Dialog open={isOpen} onClose={closeModal} className='z-50'>

        {/* Backdrop */}
        <div className='fixed inset-0 bg-zinc-200/50 dark:bg-zinc-900/50 backdrop-blur-sm cursor-pointer' aria-hidden='true' />

        <Dialog.Panel
          className={`
            ${className || `
              fixed left-1/2
              ${
                place === 'center'
                ? 'top-1/2 rounded-2xl'
                : 'bottom-0 sm:bottom-auto sm:top-1/2 rounded-t-2xl xs:rounded-b-2xl'
              }
              bg-zinc-50 dark:bg-zinc-900
              w-min
              -translate-x-1/2
              overflow-hidden
              px-2 xs:px-6
              shadow-3xl shadow-primary-400/10
            `}
            ${addClass || ''}
          `}
        >
          {children}
              
        </Dialog.Panel>

        <button
          onClick={closeModal}
          className='
            fixed top-4 right-4
            bg-primary-600 hover:bg-primary-400
            rounded-full
            h-7 w-7 desktop:hover:w-20
            overflow-x-hidden
            transition-[background-color_width] duration-300 ease-in-out
            group/button
          '
          aria-role='button'
        >
          Close
        </button>

      </Dialog>

    </>
  )
}

export default Modal

我希望这些信息有帮助。如果还有其他有帮助的信息,请告诉我。

有用的更新

谢谢 Jonathan Wieben 解释为什么这不起作用(参见解释)。该问题仅与应用样式的范围以及 Headless UI 对 React

Portal
组件的使用有关。如果有人对我如何更改
Portal
的渲染位置或更改样式的范围有一些想法,那将非常有帮助。 Jonathan Wieben 指出了一种方法来做到这一点,但是根据我的测试,它不适用于 Tailwind CSS。

javascript css next.js fonts tailwind-css
5个回答
10
投票

您正在使用的

Dialog
组件在门户中呈现(参见此处)。

您通常希望将它们呈现为 React 应用程序最根节点的同级节点。这样您就可以依靠自然的 DOM 排序来确保它们的内容呈现在您现有的应用程序 UI 之上。

您可以通过检查浏览器中的模态 DOM 元素并查看它是否确实放置在

div
组件的
App
包装器之外来确认这一点(我怀疑是这样)。

如果是这样,这就是为什么模式内容不使用预期字体呈现的解释:它是在定义字体的组件外部呈现的。

为了解决这个问题,您可以在更高级别上定义字体,例如在您的脑海中,如下所述:下一个文档


5
投票

我在 headlessui、tailwind 和 nextjs 上也遇到了完全相同的问题。 我发现正确标记的解决方案对于像模态这样简单的事情来说太复杂了。 对我有用的是将相同的字体插入模态组件中:

//Modal.tsx
import { Dialog, Transition } from '@headlessui/react';
import { Rubik } from '@next/font/google';

const rubik = Rubik({
  subsets: ['latin'],
  variable: '--font-rubik',
});

type Props = {
  children: React.ReactNode;
  isOpen: boolean;
  closeModal: any;
};

const Modal = ({ children, isOpen, closeModal }: Props) => {
  return (
  <>
  <Transition ...>
    <Dialog ...>
    ...
        <Dialog.Panel
              className={`${rubik.variable} font-sans ...`}>
              ...
        </Dialog.Panel>
    </Dialog>
  </Transition>
  </>
    );
};
export default Modal;

工作起来很有魅力。


0
投票

终于有解决方案了

...虽然不完美...

这个解决方案有效,但它不允许我们充分利用加载

next/font
。值得庆幸的是,它目前是一个简单的解决方案。

由于问题来自于

@headlessui/react

Modal
 组件渲染为 
<body>
 元素的子元素,因此我们需要在 
next/font
 元素上应用 
CSS
 生成的 
<body>
 变量,而不是 
<div>
组件中的 App
 元素,如 
next/font
 文档中所示。

不幸的是,无法像使用

<div>

 元素一样添加它们。我们需要做的是使用更普通的 
JavaScript
 方法,并在页面加载后使用 
document.querySelector('body')
className.add()
 应用类。

添加类功能(可选)

对于我的解决方案,我使用了一个名为

addClass

 的自定义函数。这可能不是必需的,但是当我第一次尝试
body.classList.add(typefaceClasses)
时,它说字符串有错误的字符。

如果你想使用

addClass

功能,这里是:

/** * ### Add Class * - Adds the specified classes to the specified elements * @param {Element|HTMLElement|HTMLElement[]|NodeList|string|undefined} elements An HTML Element, an array of HTML Elements, a Node List, a string (as a selector for a querySelector) * @param {string|string[]} classes A string or an array of classes to add to each element */ export const addClass = (elements: Element | HTMLElement | HTMLElement[] | NodeList | string, classes: string | string[]) => { const elementsType = elements.constructor.name, classesType = classes.constructor.name let elementList: HTMLElement[] | undefined, classesList: string[] | undefined // * Convert elements to array // @ts-ignore elementsType verifies type if (elementsType === 'String') elementList = Array.from(document.querySelectorAll(elements)) // Selector // @ts-ignore elementsType varfies type if (elementsType.startsWith('HTML')) elementList = [elements] // One HTML Element // @ts-ignore elementsType verifies type if (elementsType === 'NodeList') elementList = Array.from(elements) // Multiple HTML Elements // @ts-ignore elementsType verifies type if (elementsType === 'Array') elementList = elements // Array of Elements // * Convert classes to array // @ts-ignore classesType verifies type if (classesType === 'String' && classes.split(' ')) classesList = classes.split(' ') // @ts-ignore classesType verifies type if (classesType === 'Array') classesList = classes if (elementList && classesList) elementList.forEach((element: HTMLElement) => classesList!.forEach((classItem: string) => { if (hasClass(element, classItem)) return element.classList.add(classItem) }) ) }
向主体添加类

正如您在下面的示例中看到的,我们使用

useEffect


// app.tsx import '@/styles/globals.css' import type { AppProps } from 'next/app' import { Inter } from 'next/font/google' const inter = Inter({ subsets: ['latin'], variable: '--font-inter' }) import localFont from 'next/font/local' const appTakeoff = localFont({ src: [ { path: '../fonts/app-takeoff/regular.otf', weight: '400', style: 'normal' }, { path: '../fonts/app-takeoff/regular.eot', weight: '400', style: 'normal' }, { path: '../fonts/app-takeoff/regular.woff2', weight: '400', style: 'normal' }, { path: '../fonts/app-takeoff/regular.woff', weight: '400', style: 'normal' }, { path: '../fonts/app-takeoff/regular.ttf', weight: '400', style: 'normal' } ], variable: '--font-app-takeoff' }) import { useEffect, useMemo } from 'react' import { addClass } from '@/utils/class-management' const App = ({ Component, pageProps }: AppProps) => { // Set an array of the classes (use string with classList.add()) const typefaceClasses = useMemo(() => [ inter.variable, appTakeoff.variable, 'font-sans' ], []) useEffect(() => { // First we make sure the window is defined if (typeof window) { // Get the body element const body = document.querySelector('body') // If the body element is truthy, we add all of the classes to it // Otherwise null body ? addClass(body, typefaceClasses) : null } }, [typefaceClasses]) return ( <Component {...pageProps} /> ) } export default App
接下来做什么?

希望有一天有人能提供更好的解决方案,或者 Next.js 团队可能会默认添加对

<body>

 元素的支持。


0
投票
使用应用程序目录时这不是问题。现在它已经结束测试版了,我强烈推荐使用它。


0
投票
/********* external libraries ****************/ /********* external libraries ****************/ /********* internal libraries ****************/ import { Noto_Sans_TC } from '@next/font/google'; import CustomFont from '@next/font/local'; import type { NextPage } from 'next'; import type { AppProps } from 'next/app'; import Head from 'next/head'; import type { ReactElement, ReactNode } from 'react'; import './styles.css'; /********* internal libraries ****************/ export type NextPageWithLayout<P = unknown, IP = P> = NextPage<P, IP> & { getLayout?: (page: ReactElement) => ReactNode; }; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; }; const notoSansTC = Noto_Sans_TC({ weight: ['300', '400', '700', '900'], subsets: ['chinese-traditional'], display: 'swap', }); const chappaFont = CustomFont({ src: '../public/fonts/chappa-Black.ttf', variable: '--font-chappa', }); const cubic11 = CustomFont({ src: '../public/fonts/Cubic_11_1.013_R.ttf', variable: '--font-cubic11', }); export default function CustomApp({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => page); return ( <> <style jsx global>{` .body { font-family: ${notoSansTC.style.fontFamily}; } .font-cubic11 { font-family: ${cubic11.style.fontFamily} ${notoSansTC.style.fontFamily}; } .font-chappa { font-family: ${chappaFont.style.fontFamily} ${notoSansTC.style.fontFamily}; } `}</style> <Head> <title>Welcome to Horny!</title> </Head> <div className={`${chappaFont.variable} ${cubic11.variable} ${notoSansTC.className}`} > {getLayout(<Component {...pageProps} />)} </div> </> ); }
我使用

下一个文档下一个文档

© www.soinside.com 2019 - 2024. All rights reserved.