德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)

网站地图

搜索
德胜云咨询
前端分类 javascript CSS 正则表达式 html 前端框架 typescript Ajax
热门标签:
最新标签:

Vue Grid Layout基于Vue3的前端网格布局探索:使用JS动态生成响应式网格布局vue grid table学会了吗,

日期:2023/03/20 17:07作者:林子帆人气:

导读:提示:文章底部有完整的源代码,使用Rollup编译后总体积只有2kb左右,童鞋们有需要可以直接CTRL + C拿走。著名的UI库:bootstr...

提示:文章底部有完整的源代码,使用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的响应式网格布局了,童鞋们理解了吗?感谢阅读!

网站地图

Copyright © 2002-2022 香港德胜云网络 版权所有 | 备案号:蜀ICP备2023007363号-5

声明: 本站内容全部来自互联网,非盈利性网站仅供学习交流