这是我第一次使用新的
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。
我在 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;
工作起来很有魅力。
这个解决方案有效,但它不允许我们充分利用加载
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
接下来做什么?<body>
元素的支持。
/********* 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>
</>
);
}
我使用下一个文档和下一个文档