龙空技术网

基于Vue3的前端网格布局探索:使用JS动态生成响应式网格布局

前端梁哥 1286

前言:

如今看官们对“el输出html”可能比较重视,各位老铁们都想要分析一些“el输出html”的相关文章。那么小编同时在网摘上收集了一些有关“el输出html””的相关内容,希望咱们能喜欢,小伙伴们快快来学习一下吧!

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

标签: #el输出html #c语言网格