跳到主要内容

5 篇博文 含有标签「css」

查看所有标签

移除 Ant Design 中默认的 a 元素样式

· 阅读需 1 分钟
1adybug
子虚伊人

Ant Design 总是会给 a 元素设置默认样式,这样会导致在使用 a 元素时,样式不符合我们的预期。比如在 a 元素中设置了 colorred,但是在 Ant Design 中,a 元素的默认样式是 blue,这样就会导致我们的样式被覆盖。可以用以下方式去除默认样式:

a[href],
a[href]:active,
a[href]:hover {
color: inherit;
}

在 SSR 中按需提取 Ant Design CSS

· 阅读需 3 分钟
1adybug
子虚伊人

使用方法

react router(或者 remix)中使用 Ant Design 时,如果不对 css 进行处理,会导致首屏样式丢失的问题。之前介绍过整体导入的解决方案 在 Remix 中使用 Ant Design,但是这种方式会导致打包出来的 css 文件很大。本文介绍一种更优雅的解决方案:在 SSR 中按需提取 Ant Designcss

  1. 需要在项目中暴露 entry.client.tsxentry.server.tsx,如果已经暴露了,可以跳过这一步:

    npx react-router reveal
  2. 安装相应依赖:

    npm i @ant-design/cssinjs @ant-design/static-style-extract
  3. root.tsx 中放入 __ANTD_STYLE_PLACEHOLDER__

    const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"

    const isDev = process.env.NODE_ENV === "development"

    export const Layout: FC<PropsWithChildren> = ({ children }) => (
    <html lang="zh">
    <head>
    <meta charSet="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <Meta />
    <Links />
    {/** 如果需要在开发环境开启,去除 !isDev */}
    {!isBrowser && !isDev && "__ANTD_STYLE_PLACEHOLDER__"}
    </head>
    <body>
    <ConfigProvider
    theme={{
    token: {
    colorPrimary: "#FF0000",
    },
    }}
    >
    {children}
    </ConfigProvider>
    <ScrollRestoration />
    <Scripts />
    </body>
    </html>
    )
  4. 修改 entry.client.tsx

    import { startTransition, StrictMode } from "react"
    import { hydrateRoot } from "react-dom/client"

    import { legacyLogicalPropertiesTransformer, StyleProvider } from "@ant-design/cssinjs"
    import { HydratedRouter } from "react-router/dom"

    startTransition(() => {
    hydrateRoot(
    document,
    <StrictMode>
    <StyleProvider transformers={[legacyLogicalPropertiesTransformer]} hashPriority="high">
    <HydratedRouter />
    </StyleProvider>
    </StrictMode>,
    )
    })
  5. 修改 entry.server.tsx

    import { type RenderToPipeableStreamOptions, renderToPipeableStream } from "react-dom/server"

    import { PassThrough } from "node:stream"

    import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs"
    import { createReadableStreamFromReadable } from "@react-router/node"
    import { isbot } from "isbot"
    import { type AppLoadContext, type EntryContext, ServerRouter } from "react-router"

    const ABORT_DELAY = 5_000

    export default function handleRequest(
    request: Request,
    responseStatusCode: number,
    responseHeaders: Headers,
    routerContext: EntryContext,
    loadContext: AppLoadContext,
    ) {
    return new Promise((resolve, reject) => {
    let shellRendered = false
    const userAgent = request.headers.get("user-agent")

    const fromBot = !!userAgent && isbot(userAgent)

    // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
    // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
    const readyOption: keyof RenderToPipeableStreamOptions = (userAgent && isbot(userAgent)) || routerContext.isSpaMode ? "onAllReady" : "onShellReady"

    const cache = createCache()

    const { pipe, abort } = renderToPipeableStream(
    <StyleProvider cache={cache}>
    <ServerRouter context={routerContext} url={request.url} abortDelay={ABORT_DELAY} />
    </StyleProvider>,
    {
    [readyOption]() {
    shellRendered = true

    const body = new PassThrough({
    transform(chunk, encoding, callback) {
    chunk = String(chunk).replace("__ANTD_STYLE_PLACEHOLDER__", fromBot ? "" : extractStyle(cache))
    callback(null, chunk)
    },
    })

    const stream = createReadableStreamFromReadable(body)

    responseHeaders.set("Content-Type", "text/html")

    resolve(
    new Response(stream, {
    headers: responseHeaders,
    status: responseStatusCode,
    }),
    )

    pipe(body)
    },
    onShellError(error: unknown) {
    reject(error)
    },
    onError(error: unknown) {
    responseStatusCode = 500

    // Log streaming rendering errors from inside the shell. Don't log
    // errors encountered during initial shell rendering since they'll
    // reject and get logged in handleDocumentRequest.
    if (shellRendered) console.error(error)
    },
    },
    )

    setTimeout(abort, ABORT_DELAY)
    })
    }

原理分析

  1. 首先是在服务端的 HTML 代码中插入了 __ANTD_STYLE_PLACEHOLDER__

    !isBrowser && !isDev && "__ANTD_STYLE_PLACEHOLDER__"
  2. 然后是将 antd 的样式抽取为 style 标签

    const cache = createCache()

    renderToPipeableStream(
    <StyleProvider cache={cache}>
    <ServerRouter context={routerContext} url={request.url} abortDelay={ABORT_DELAY} />
    </StyleProvider>,
    )

    const css = extractStyle(cache)
  3. __ANTD_STYLE_PLACEHOLDER__ 替换为抽取的 style 标签

    chunk = String(chunk).replace("__ANTD_STYLE_PLACEHOLDER__", fromBot ? "" : extractStyle(cache))

:last-child 与 :last-of-type 的区别

· 阅读需 2 分钟
1adybug
子虚伊人

:last-child:last-of-type 的区别:

  1. :last-child 选择器

    • 选择父元素中最后一个子元素
    • 无论这个子元素是什么类型
    • 如果最后一个子元素不匹配选择器,则不会被选中
  2. :last-of-type 选择器

    • 选择父元素中最后一个指定类型的子元素
    • 只关注特定类型的元素
    • 即使是最后一个,但不是指定类型也不会被选中

举个例子:

<div>
<p>第一段</p>
<p>第二段</p>
<span>一个span</span>
</div>
  • div p:last-child 不会选中任何元素(因为最后一个子元素是 <span>
  • div p:last-of-type 会选中第二个 <p> 元素
  • div span:last-of-type 会选中最后的 <span>

简单来说:

  • :last-child 更严格,要求是最后一个子元素
  • :last-of-type 更灵活,只要是同类型的最后一个元素
提示

:last-of-type 只关注元素类型,不关注类名、ID 等属性,所以不会受到这些属性的影响。

比如:

<div>
<p class="red">第一段</p>
<p>第二段</p>
<p>第三段</p>
</div>

div p.red:last-of-type 不会选中任何元素,因为 p:last-of-type 已经选中最后一个 <p> 元素,但是这个元素没有 red 类名。

设置滚动条样式

· 阅读需 1 分钟
1adybug
子虚伊人

很多时候,我们希望设置滚动条的样式,但是设置的前提是,我们设置了滚动条的宽度:

/* 设置滚动条的整体样式 */
::-webkit-scrollbar {
/* 滚动条的宽度,纵向滚动时有效 */
width: 12px;
/* 滚动条的高度,横向滚动时有效 */
height: 12px;
}

/* 设置滚动条滑块的样式 */
::-webkit-scrollbar-thumb {
/* 滑块的颜色 */
background-color: rgba(0, 0, 0, 0.25);
/* 滑块的圆角 */
border-radius: 6px;
}

/* 当鼠标悬停在滑块上时,改变滑块的颜色 */
::-webkit-scrollbar-thumb:hover {
/* 滑块的颜色 */
background: rgba(0, 0, 0, 0.45);
}

/* 设置滚动条轨道的样式 */
::-webkit-scrollbar-track {
/* 滚动条轨道的颜色 */
background: rgba(0, 0, 0, 0.05);
/* 滚动条轨道的圆角 */
border-radius: 6px;
}

/* 滚动条角落的颜色 */
::-webkit-scrollbar-corner {
/* 滚动条角落的颜色 */
background: rgba(0, 0, 0, 0.05);
}

如何这些样式不生效,还有可能是元素设置了 scrollbar-colorscrollbar-width 属性,导致浏览器默认样式覆盖了我们的样式,所以需要将这些属性设置为 auto

在网页中引入字体

· 阅读需 1 分钟
1adybug
子虚伊人
@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-35-Thin.woff2") format("woff2");
font-weight: 100;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-45-Light.woff2") format("woff2");
font-weight: 200;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-55-Regular.woff2") format("woff2");
font-weight: 300;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-65-Medium.woff2") format("woff2");
font-weight: 400;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2") format("woff2");
font-weight: 500;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-85-Bold.woff2") format("woff2");
font-weight: 600;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-95-ExtraBold.woff2") format("woff2");
font-weight: 700;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-105-Heavy.woff2") format("woff2");
font-weight: 800;
}

@font-face {
font-family: "AlibabaPuHuiTi";
src: url("./fonts/AlibabaPuHuiTi-3-115-Black.woff2") format("woff2");
font-weight: 900;
}