网站首页 > 基础教程 正文
提示:文章底部有完整的源代码,使用Rollup编译后总体积只有2kb左右,童鞋们有需要可以直接CTRL + C拿走。
我想,前端的童鞋们应该都了解或使用过网格布局吧?著名的UI库:bootstrap,element-ui,iview等都提供了网格布局。我们所熟知的这些库都是采用的CSS预处理语言生成的网格布局。这是一种主流的实现方案,也很符合前端开发的一个格言:能用CSS实现的,尽量不要用JS实现。
动机:熟悉网格布局实现原理的童鞋们应该都比较清楚,网格布局的CSS代码量是很大的,轻而易举就能达到30kb以上;虽然我们是用less这样的预处理语言写的,拥有编程语言的一些能力,可以利用循环和变量来大幅减小我们所编写的代码量;但是,最终浏览器加载的依旧是生成的CSS。这个体积嘛,像我这种对体积非常敏感的人,感觉有一点点儿大。那么,我们有没有一种方式,可以大幅减小网格布局的体积呢?这正是本篇文章的主题:使用JS动态生成响应式网格布局。
我们先看两张效果图,这是element-ui官方文档Row和Col组件的例子没有做任何修改,直接在本篇文章实现的组件下的效果。
要使用JS生成网格布局,我们需要动态创建样式表;使用JS创建样式表是很简单的事情,只需要创建一个style元素,然后将样式表字符串添加到style元素,最后将style元素添加到head即可。如下是createStylesheet函数的定义:
export function createStylesheet (id, styleSheetStr) {
let el = document.getElementById(id)
// 避免重复创建相同的样式表,只有不存在的时候才创建
if (!el) {
el = document.createElement('style')
el.id = id
el.innerHTML = styleSheetStr
document.head.appendChild(el)
}
}
我们采用的网格布局是24分栏,和element-ui保持一致,这也是目前最主流的网格布局分栏数量。我们需要生成0-24共25列,当列占用的空间为0的时候处于隐藏状态。现在,我们先创建一个包含25个元素的数组,我们不需要关注数组中元素的值,我们只会用到元素的索引。之所以使用数组,是因为我不想使用for循环,而更偏向于数组的遍历方法。
const nulls = new Array(25).fill(null)
现在,我们定义一个获取列宽度的函数getSpan,当列数为0的时候,将元素设置为不可见。
const getSpan = (i, val) => i ? `width:${val}` : 'display:none'
然后,我们创建用于生成列的函数genCol,该函数将列数转化为百分比,以实现弹性的宽度。不知道童鞋们有没有被left和right搞懵呢?[呆无辜]
export const cls = 'x-col' // class前缀
const genCol = () => nulls.map((_, i) => {
const val = `${i / 24 * 100}%`
return [
`.${cls}_span-${i}{${getSpan(i, val)};}`, // 列宽
`.${cls}_pull-${i}{right:${val};}`, // 向左移动的宽度
`.${cls}_push-${i}{left:${val};}`, // 向右移动的宽度
`.${cls}_offset-${i}{margin-left:${val};}` // 向右的偏移宽度
].join('')
}).join('')
目前,我们生成的布局不是响应式的,不管屏幕有多宽,都会占用固定的百分比宽度。那么,我们如何使布局变成响应式的呢?媒体查询,该你出场了。
现在,我们先定义一个根据窗口宽度生成布局的函数genColBySize。这个函数和上面的genCol函数长的很像,只是class名称中添加了一个size,童鞋们应该都能理解吧?
const genColBySize = size => nulls.map((_, i) => {
const val = `${i / 24 * 100}%`
return [
`.${cls}_${size}-span-${i}{${getSpan(i, val)};}`,
`.${cls}_${size}-pull-${i}{right:${val};}`,
`.${cls}_${size}-push-${i}{left:${val};}`,
`.${cls}_${size}-offset-${i}{margin-left:${val};}`
].join('')
}).join('')
我们与element-ui保持一致,将响应式断点设置为5个,分别是: xs,sm,md,lg,xl;现在,我们生成响应式布局,并导出一个添加样式表函数addStylesheet。
const genResponsiveCol = () => [
['xs'],
['sm', 768],
['md', 992],
['lg', 1200],
['xl', 1920]
].map(_ => _[1]
? `@media (min-width:${_[1]}px){${genColBySize(_[0])}}`
: genColBySize(_[0])
).join('')
// 为什么没有写在addStylesheet里面?
// 是为了减少2个生成函数的调用次数,避免不必要的调用,现在只会被调用一次
const ruleStr = genCol() + genResponsiveCol()
export const addStylesheet = () => {
createStylesheet('XGridLayout', ruleStr)
}
以上就是使用JS生成响应式网格布局的全部核心代码,是不是很简单?现在,我把Row和Col组件的剩余代码提供给童鞋们,为了节省篇幅,把空行去掉了,但可读性还是很高的。希望阅读过本篇文章的童鞋们都能够自己动手实现。
Col.vue组件源码:
<template>
<div :class="classes" :style="styles">
<slot />
</div>
</template>
<script setup>
import { computed, inject, onMounted } from 'vue'
// N: Number, N0: { type: Number, default: 0 }
import { N, N0 } from '../../types'
import { addStylesheet, cls } from './utils'
const props = defineProps({
span: { type: N, default: 24 },
offset: N0,
push: N0,
pull: N0,
xs: {},
sm: {},
md: {},
lg: {},
xl: {}
})
const classes = computed(() => {
const clsList = [cls]
;['span', 'offset', 'push', 'pull'].forEach(k => {
const v = +props[k]
v && clsList.push(`${cls}_${k}-${v}`)
})
;['xs', 'sm', 'md', 'lg', 'xl'].forEach(k => {
const v = props[k]
if (v) {
const opts = +v ? { span: +v } : v
Object.keys(opts).forEach(k2 => {
clsList.push(`${cls}_${k}-${k2}-${opts[k2]}`)
})
}
})
return clsList
})
const gutter = inject('gutter') // 响应式的数值,由Row组件提供,注入到Col组件
const styles = computed(() => {
const padding = `${gutter.value / 2}px`
return gutter.value && { paddingLeft: padding, paddingRight: padding }
})
onMounted(() => {
addStylesheet()
})
</script>
Row.vue组件源码:
<template>
<div :class="classes" :style="styles">
<slot />
</div>
</template>
<script setup>
import { computed, provide, toRefs } from 'vue'
// N0: { type: Number, default: 0 }, oneOf: (arr, v) => arr.includes(v)
import { N0, oneOf } from '../../types'
const props = defineProps({
gutter: N0,
justify: {
default: 'start',
validator: v => oneOf(['start', 'end', 'center', 'space-around', 'space-between'], v)
},
align: {
validator: v => oneOf(['top', 'middle', 'bottom'], v)
}
})
const { gutter } = toRefs(props) // gutter是响应式的
provide('gutter', gutter) // 提供给子组件使用
const classes = computed(() => {
const cls = 'x-row'
return [
cls,
props.align && `${cls}_${props.align}`,
props.justify && `${cls}_${props.justify}`,
{ gutter: props.gutter }
]
})
const styles = computed(() => props.gutter && { margin: `0 -${props.gutter / 2}px` })
</script>
Col组件样式是通过JS生成的,我们只需要row.scss样式文件就够了,这里是源码:
.x-row {
display: flex;
&_top {
align-items: flex-start;
}
&_middle {
align-items: center;
}
&_bottom {
align-items: flex-end;
}
&_start {
justify-content: flex-start;
}
&_end {
justify-content: flex-end;
}
&_center {
justify-content: center;
}
&_space-around {
justify-content: space-around;
}
&_space-between {
justify-content: space-between;
}
}
.x-col {
word-wrap: break-word;
}
现在我们可以实现体积只有2kb的响应式网格布局了,童鞋们理解了吗?感谢阅读!
- 上一篇: JS面试题:公司真实js面试题整理 js面试大全
- 下一篇: 不得不知的网络安全知识 网络安全知识科普
猜你喜欢
- 2024-12-22 一大波开源小抄来袭 开源小说软件下载
- 2024-12-22 「Electron跨平台桌面应用开发 4」系统托盘功能
- 2024-12-22 超级简单 Bing美图每天自动收 收藏美图
- 2024-12-22 使用Python进行并发编程 python 并发编程
- 2024-12-22 我的世界计分板命令创建队伍教程详解
- 2024-12-22 Webpack5 配置手册(从0开始) webpack简单配置
- 2024-12-22 electron开发桌面应用实现串口通信,看完你就学会了
- 2024-12-22 JavaScript中原生的原型 Prototype
- 2024-12-22 webpack系列学习-基本用法 webpack基本使用
- 2024-12-22 前端技术探秘-Nodejs的CommonJS规范实现原理
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)