移动端 Flexbox gap 兼容 ios14 以下的变通

要实现上图这样的布局,乍一看很简单,用 flex 或 grid 就能轻松搞定,但假如因为兼容性不能用 grid 布局的情况下,又该怎么实现呢?你可以尝试实现一下,可能实际写的过程中会发现比想象的要复杂一些,这里提供一个基础代码模版,你只需要写 css 布局。
梳理一下,你需要考虑一下几点:
- 各个尺寸手机适配
- 个数不固定,每一个 item 项都是动态的,有条件的显示隐藏
- 一排四个,两端对齐,超过自动换行显示
- 兼容 ios13、chrome49
flex gap 兼容性如何?
通过 caniuse 可以查到 flexbox gap chorme最低是84,ios最低是 14.1,达不到我们的兼容性要求,所以需要兼容性的实现方式。
基本的思路就是支持 flexbox gap 的就用 flexbox gap,不支持的就用 margin 来实现。
flex gap 如何实现?
我们先处理好基础的 html 代码,方便后面写css。
<template>
<div class="content">
<button type="button" @click="count++">增加一项</button>
<button type="button" @click="count--">减少一项</button>
<div class="list-container">
<div class="list-item" v-for="item in count">
<div class="icon"></div>
<div class="name">{{ item }}</div>
</div>
</div>
<div class="border"></div>
<div class="list-container">
<div class="list-item" v-for="item in count">
<div class="icon"></div>
<div class="name">{{ item }}</div>
</div>
</div>
</div>
</template>
移动端我们需要用到 rem 布局,所以需要处理一下 rem 相关逻辑,还有动态个数的逻辑。
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(6)
function setupRem() {
const baseDesignWidth = 375
const baseFontSize = 16
const clientWidth = window.innerWidth
const remBaseFontSize = (clientWidth / baseDesignWidth) * baseFontSize
document.documentElement.style.fontSize = `${remBaseFontSize}px`
}
setupRem()
window.addEventListener('resize', setupRem)
</script>
接下来就是重点 css 的实现。这里,我们先明确一下 gap 和 margin 的区别。
gap 属性是用来设置行与列之间的间隙,他和 margin 设置间隙有个浅显的最大不同是元素有换行时,我们不需要给换行的元素设置 margin: 0
来消除间隙,而 gap 设置的是元素的网格间隙(Gutter),只会在网络轨道之间产生间距,所以我们不需要关注换行时的间隙重置。
我们知道每个 item 项的最大宽度是固定的,间距的计算就是重中之重了,如何计算呢?其实很简单,总共四列,我们可以算出容器宽度(100%)减去item项宽度总和(元素宽度 * 列数),再除以列数-1就得到间距了。
:root {
/* item 项最大宽度 */
--item-max-width: 5rem;
/* 图标大小 */
--icon-size: 3rem;
/* 列数 */
--column-count: 4;
/* 垂直间距 */
--row-gap: 1rem;
/* icon 背景颜色 */
--icon-bg: #e1d5e7;
/* name 背景颜色 */
--name-bg: #f1f1f1;
/* 边框线颜色 */
--border-color: #999;
/* 这里是重点 */
/* 每个 item 所占宽度总和 */
--total-item-width: calc(var(--item-max-width) * var(--column-count));
/* 水平间距 = (容器宽度 - item宽度总和) / (列数 - 1) */
--column-gap: calc((100% - var(--total-item-width)) / (var(--column-count) - 1));
}
最关键的搞定了,现在快速完善一下剩下的 css 代码。
.content {
padding: 0 1rem;
}
.list-container {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
padding: 1rem 0;
row-gap: var(--row-gap);
column-gap: var(--column-gap);
background-clip: content-box;
}
.border {
width: 100%;
height: 1px;
transform: scaleY(0.5);
background-color: var(--border-color);
}
.list-container .list-item {
width: var(--item-max-width);
height: var(--item-max-width);
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.list-container .list-item .icon {
width: var(--icon-size);
height: var(--icon-size);
background: var(--icon-bg);
border-radius: 100%;
}
.list-container .list-item .name {
font-size: 0.8rem;
text-align: center;
background: var(--name-bg);
width: 100%;
}
现在效果已经实现了,接下来考虑兼容性的实现方式,以便在设备不支持 flexbox gap 布局时生效,由于 gap 属性支持度比 flexbox gap 要好得多,通过下图 caniuse 也可以看到,所以如何判断设备支不支持就需要用到一些 hack 技巧,先贴出代码实现,再解释原理。
@supports (-webkit-touch-callout: none) and (not (translate: none)) {
.list-container .list-item:nth-of-type(n + 5) {
margin-top: var(--row-gap);
}
.list-container .list-item:not(:nth-of-type(4n)) {
margin-right: var(--column-gap);
}
}
@supports
是一个 CSS 规则,它可以检查浏览器是否支持某个 CSS 属性及其值。-webkit-touch-callout
是一个非标准的 CSS 属性,主要用于控制当你触摸并持续按住链接或可点击元素时是否显示系统默认的菜单。它只在 iOS 和旧的 Safari 版本上支持。
将这两个结合,这个逻辑的背后原理大致如下:
-webkit-touch-callout
是一个只有 WebKit(Safari 浏览器的引擎)才有的特性,在其他浏览器中是不支持的,所以它可以作为一个指示符来判断用户的浏览器是否是 Ios Safari。- translate 是一个比较新的属性,通过下图可以看到,比较新的浏览器才支持,所以我们可以反向这个条件,把不支持这个属性作为条件之一,从而判断旧版浏览器。
至此,大功告成,可以点击此处预览效果以及完整的代码。
转载自:https://juejin.cn/post/7357261180493627418