前言:
如今看官们对“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的响应式网格布局了,童鞋们理解了吗?感谢阅读!