tailwind“移动端优先”在隐藏元素方面的问题 - tailwindcss 系列

本文从一个小小的需求扩展到更灵活可读性更好的解决方案。写法总数从 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 2 N ) → O ( 1 ) O(2N) \rightarrow O(1) </math>O(2N)→O(1) 🎉。

🤕 问题

tailwind css 推崇移动端优先原则:

移动端优先

Tailwind 采用移动优先的断点体系,与 Bootstrap 等框架类似。

即:无前缀的类(如 uppercase)在所有屏幕尺寸生效;带前缀的类(如 md:uppercase)仅在对应断点及以上尺寸生效。

------ tailwindcss.com/docs/respon...

也就是说默认情况我们的样式是针对移动端的,如果需要适配宽屏需要增加前缀 md:lg: 等,看起来挺好的,但是在隐藏元素方面存在一些不方便。

假如元素想在移动端隐藏,但是其他屏幕展示,按照此原则我们应该怎么写呢?

  • block 元素如 <div>, <p>, <h1>-<h6>, <ul>, <li>hidden md:block
  • inline 元素如 <span>, <a>, <strong>, <em>, <img>hidden md:inline
  • inline-block 元素如 <button>, <input>, <select>, <textarea>hidden md:inline-block
  • ...

如果应用了 flex 或 grid

  • flex:hidden md:flexhidden md:inline-flex
  • grid:hidden md:gridhidden md:inline-grid
  • ...

上面只是列举了一部分,出现了两个问题,DRY 以及 display 的值难以简单决定。

问题一:DRY - 插件解决

重复的模式代码 hidden md:block hidden md:inline hidden md:inline-block ......。

要是能直接写一个 class 就好了,比如 hidden-mobile-block hidden-mobile-inline hidden-mobile-inline-block ......(如果类名长你可以缩写,但是为了可读性不建议)。

可以通过自定义 tailwind 插件解决 ,思路就是通过插件生成一系列固定前缀的 class hidden-mobile-xxx,优点可以智能提示以及支持任意值(hidden-mobile-[table]),具体可看我的这篇文章:写一个根据屏幕尺寸动态隐藏元素的插件 🧩 - tailwindcss 系列

问题二:display 变化多端

第二个问题 display 属性实在太多,其次我们不仅要记住 divblock 元素,spaninlinebuttoninline-block而且如果 display 被修改成其他布局比如 flex 那就得看应用后的最终样式(你可以在 Chrome 的 devtool 的 style computed 模块看到),即我们需要知道当前元素的 display 值才能使用,要是能自动识别就好。

问题二解法:智能识别?

根据元素最终布局自动精准生成 display

即我们连 hidden-mobile-block hidden-mobile-inline hidden-mobile-inline-block 都无需写,只需统一写 hidden-mobile

比如 span 上应用 class hidden-mobile 自动生成 hidden-mobile-inline,div 则 block......。但 tailwind 插件还没有如此强大并不能"反射"拿到被其附着的元素,而且有时候拿到元素不一定就能判断其 display,因为父元素或者自己本身的 className 也会影响自己,这就需要运行时动态计算了,并非插件能力范围(假设可以拿到附着的元素,可以将其在 Node.js 无头浏览器渲染或者在实际 DOM 渲染完毕后动态添加样式,但是性能会有很大影响,前者影响编译时后者影响运行时)。

小结:无法智能识别

🌟 更好的解法:通过 tailwind 的 max-md:hidden

逆向思维 >=<。通过 max-md:hidden 来达到我们目的,< 768px 则 hidden,编译后:

css 复制代码
@media not all and (min-width: 768px) {
  .max-md:hidden {
    display: none;
  }
}

代码解释:min-width: 768px>= 768 取反 not all 就变成 < 768px

为什么 tailwind 要这么编译?不就等价于 max-width: 767px 吗?注意是 768-1 否则就不等价了。

  1. 不能使用已有断点,需要做减法。
  2. not 语法意图更明确,即取反 >= 直接变成 <

写法对比:

tsx 复制代码
# 原始写法 O(2N)
<div className="hidden md:block">只适合宽屏显示的内容<div/>
# 自定义插件写法 O(N)
<div className="mobile-hidden-block">只适合宽屏显示的内容<div/>
# 范围写法 O(1)
<div className="max-md:hidden">只适合宽屏显示的内容<div/>

写法总数从 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 2 N ) → O ( 1 ) O(2N) \rightarrow O(1) </math>O(2N)→O(1) 🎉:

看看前后的对比,你就能感受到它的简洁,只需一个类名无需写 display,没有性能影响。

参考 Targeting a breakpoint range ~ Tailwind CSS

还有问题吗?

如果你"吹毛求疵" max-md:hidden 仍然还存在两个问题,可读性不好和临界值问题。你第一眼能看出下面的代码是在移动端显示还是隐藏吗?

html 复制代码
<div className="max-md:hidden">...<div/>

我们还是可以通过插件自定义可读性更好的类名:

js 复制代码
// @ts-check
// tailwind-plugins\hidden-on-mobile.js
const plugin = require('tailwindcss/plugin')

module.exports = plugin(function ({ addUtilities }) {
  const lessThanMdHidden = {
    // < 768 隐藏
    '@apply max-md:hidden': {},
  }

  addUtilities({
    // 768 认为是宽屏,非移动端
    '.show-on-mobile': {
      // >= 768 隐藏
      '@apply md:hidden': {},
    },

    '.hide-on-mobile': lessThanMdHidden,
    '.show-on-tablet-and-up': lessThanMdHidden,
    '.show-on-not-mobile': lessThanMdHidden,
  })
})

现在好多了,我们做了两个重命名,以及一些 alias,现在可读性是不是变好了?

html 复制代码
<div className="show-on-mobile">只适合移动端显示的内容<div/>

<div className="show-on-tablet-and-up">只适合宽屏显示的内容<div/>
<div className="show-on-not-mobile">只适合宽屏显示的内容<div/>
<div className="hide-on-mobile">只适合宽屏显示的内容<div/>

第二,临界值问题是指,假设我们认为 768px 需要和移动端保持一致风格 ,那么 md:hiddenmax-md:hidden 结合使用时在临界值 768px 是会出现问题。

html 复制代码
<div className="md:hidden">只适合移动端显示的内容<div/>
<div className="max-md:hidden">只适合宽屏显示的内容<div/>

如果尺寸刚好 等于 md 768px 页面会如何展示?将展示只适合宽屏显示的内容,即并没有按照我们的预期。 这里我们需要重写我们的插件,而且只能使用 media query:

js 复制代码
// tailwind-plugins\hidden-on-mobile.js
// @ts-check
const plugin = require('tailwindcss/plugin')

module.exports = plugin(function ({ theme, addUtilities }) {
  /** @type {`${number}px`} */
  const mdBreakpoint = theme('screens.md')
  // console.log('mdBreakpoint:', mdBreakpoint) // 768px

  const breakpoint769 = `${parseInt(mdBreakpoint) + 1}px` // 769px

  addUtilities({
    // 768 认为是移动端
    // >= 769 隐藏
    '.show-on-mobile': {
      [`@media (min-width: ${breakpoint769})`]: {
        display: 'none',
      },
    },

    // <= 768 隐藏
    '.hide-on-mobile': {
      [`@media (max-width: ${mdBreakpoint})`]: {
        display: 'none',
      },
    },
  })
})

🎯 总结

移动端隐藏其他屏幕展现若使用 hidden md:block 这个基于 tailwind 移动端优先的设计理念的写法,我们首先通过插件解决了要写多个类名问题(写法从 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 2 N ) → O ( N ) O(2N) \rightarrow O(N) </math>O(2N)→O(N)),但我们仍然需要基于当前元素在展示状态下的 display 值来写 md:xxx 的后半部分,易出错,我们逆向思维通过 max-md:hidden 进一步统一写法,写法从 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( N ) → O ( 1 ) O(N) \rightarrow O(1) </math>O(N)→O(1),最后我们继续优化通过插件重命名解决了可读性问题!

html 复制代码
# 原始写法 O(2N) ↓
<div className="hidden md:block">只适合宽屏显示的内容<div/>
# 自定义插件写法 O(N) ↓
<div className="mobile-hidden-block">只适合宽屏显示的内容<div/>
# 范围写法 O(1) ↓
<div className="max-md:hidden">只适合宽屏显示的内容<div/>
# 可读性更好的写法 O(1) ↓
<div className="show-on-tablet-and-up">只适合宽屏显示的内容<div/>
相关推荐
谢尔登2 小时前
【React Native】布局和 Stack 、Slot
javascript·react native·react.js
几颗流星4 小时前
01 react入门
前端·react.js
遂心_5 小时前
React Fragment与DocumentFragment:提升性能的双剑合璧
前端·javascript·react.js
混水的鱼5 小时前
PasswordValidation 密码校验组件实现与详解
前端·react.js
自己记录_理解更深刻5 小时前
默认导出 vs 具名导出
react.js
WildBlue5 小时前
小白也能懂!react-router-dom 超详细指南🚀
前端·react.js
颜酱6 小时前
使用useReducer和Context进行React中的页面内部数据共享
前端·javascript·react.js
混水的鱼6 小时前
React + antd 实现文件预览与下载组件(支持图片、PDF、Office)
前端·react.js
爱编程的喵7 小时前
React状态管理:从useState到useReducer的完美进阶
前端·react.js