Appearance
低代码项目样式调研
渲染区样式隔离
在做渲染区的时候,渲染的组件是从物料组件库中引入的,所以需要考虑样式隔离的问题。
- 影子 dom(缺点:需要编写大量的 js)
- iframe
- 优点:样式完全隔离 沙盒化
- 缺点:对于该项目需要操作元素拖拽,数据通信很麻烦,后期维护成本高)
- 最佳方案:Vue 的 scope + bem 命名规范
BEM 命名规范
BEM(Block, Element, Modifier)是一种 CSS 命名规范,旨在提高代码的可读性和可维护性。BEM 规范通过明确的命名规则来定义组件和组件的各个部分,使开发者能够更容易地理解和维护代码。
BEM 命名规范的基本概念
- Block(块):代表一个独立的组件,类似于一个功能模块。例如,一个导航栏或按钮。
- Element(元素):代表块的组成部分,与块紧密相关,但不能单独存在。例如,按钮中的图标或导航栏中的菜单项。
- Modifier(修饰符):代表块或元素的不同状态或变体,用于修改块或元素的外观或行为。例如,按钮的大小或颜色变化。
命名规则
- 双下划线(__):用于连接块和元素。
- 双破折号(--):用于连接块或元素与修饰符。
例如:
js
button__element--disabled
使用 Sass 生成 BEM
ts
export const NAMESPACE = 'xt'
export function createNamespace(name: string) {
// 组件前缀
const prefixName = `${NAMESPACE}-${name}`
return createBEM(prefixName)
}
// 提供生成不同bem的方法
function createBEM(prefixName: string) {
const b = (blockSuffix?: string) => _bem({ prefixName, blockSuffix })
const e = (element?: string) => element && _bem({ prefixName, element })
const m = (modifier?: string) => modifier && _bem({ prefixName, modifier })
const be = (blockSuffix?: string, element?: string) =>
blockSuffix && element && _bem({ prefixName, blockSuffix, element })
const bm = (blockSuffix?: string, modifier?: string) =>
blockSuffix && modifier && _bem({ prefixName, blockSuffix, modifier })
const em = (element?: string, modifier?: string) =>
element && modifier && _bem({ prefixName, element, modifier })
const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
blockSuffix &&
element &&
modifier &&
_bem({ prefixName, blockSuffix, element, modifier })
const is = (name: string, state: boolean) => state && `is-${name}`
return {
b,
e,
m,
be,
bm,
em,
bem,
is
}
}
// 生成bem
function _bem({
prefixName,
blockSuffix,
element,
modifier
}: {
prefixName: string
blockSuffix?: string
element?: string
modifier?: string
}) {
return (
`${prefixName}` +
`${blockSuffix ? `-${blockSuffix}` : ''}` +
`${element ? `__${element}` : ''}` +
`${modifier ? `--${modifier}` : ''}`
)
}
/**
*
const bem = createNamespace('button')
console.log(bem.b()) // xt-button
console.log(bem.b('box')) // xt-button-box
console.log(bem.e('element')) // xt-button__element
console.log(bem.m('primary')) // xt-button--primary
console.log(bem.be('icon', 'element')) // xt-button-icon__element
console.log(bem.bm('box', 'primary')) // xt-button-box--primary
console.log(bem.em('element', 'primary')) // xt-button__element--primary
console.log(bem.bem('box', 'element', 'primary')) // xt-button-box__element--primary
console.log(bem.is('disabled', true)) // is-disabled
*/
scss
@use './common.scss' as *; // * 表示导入全部
/**
common.scss 导入的内容:
$namespace: 'xt';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
*/
@mixin b($block) {
$b: $namespace + '-' + $block;
.#{$b} {
@content;
}
}
// @at-root:将选择器的上下文提升到根级别
@mixin e($element) {
@at-root {
#{& + $element-separator + $element} {
@content;
}
}
}
@mixin m($modifier) {
@at-root {
#{& + $modifier-separator + $modifier} {
@content;
}
}
}
@mixin is($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
渲染区相对区域响应式协议
该项目的响应式不是基于屏幕的,是基于相对区域的,所以用媒体查询不能实现,需要使用容器查询。
因为在编辑器后台(也就是低代码编辑区)中,可以配置移动端和PC端的页面,在这个时候,是只有渲染区这个容器中需要做响应式处理。
如何实现容器查询
- 方法 1. 使用
tailwindcss
的@container
容器查询来实现。但是方案尚不完善,并且大量的类名会导致后期的维护成本增大。
css
<div class="@container">
<div class="@lg:underline">
<!-- This text will be underlined when the container is larger than `32rem` -->
</div>
</div>
- 方法 2. 使用 css 的
container-type
属性来实现。
container-type 属性用于定义一个容器,容器内的元素可以根据这个容器的尺寸进行样式调整。它有以下几个取值:
- normal:这是默认值,表示该元素不是一个容器查询容器,即不会基于该元素的尺寸对其内部元素进行样式调整
- inline-size:只在水平方向建立容器
- size:水平和垂直方向都建立容器
html
<style>
/* 定义容器 */
.container {
container-type: inline-size;
width: 300px;
border: 1px solid #ccc;
margin: 20px;
padding: 10px;
}
/* 容器查询规则 */
@container (min-width: 200px) {
.item {
font-size: 18px;
color: blue;
}
}
@container (max-width: 199px) {
.item {
font-size: 14px;
color: red;
}
}
</style>
<div class="container">
<p class="item">这是一个容器内的元素。</p>
</div>
采用 Sass 编写容器查询响应式协议
核心:
scss
// 移动端 媒体查询(预览成品页面使用)
@mixin mb {
@media (max-width: 1023.9px) {
@content;
}
}
// 移动端 容器查询
@mixin mb-container {
@container (max-width: 1023.9px) {
@content;
}
}
// pc端 媒体查询
@mixin pc {
@media (min-width: 1024px) {
@content;
}
// pc端 容器查询
@container (min-width: 1024px) {
@content;
}
}
// 使用:@include res(font-size, 12, 20);
// $property 属性
@mixin res($property, $mobile: null, $pc: null) {
// $unitless:是 Sass 中的一个内置函数,用于判断给定的值是否没有单位,返回 true 或 false
$pcValue: if(unitless($pc), #{$pc}px, $pc);
// vw:将 px 转换为 vw,在 _functions.scss 中定义
// @function vw($px) {
// @return math.div($px, 375) * 100; // math.div 表示除
// }
// vw给预览成品页面使用
$mobileValue: if(unitless($mobile), vw($mobile), $mobile);
// 给渲染区容器查询使用(不需要vw)
$mobileContainerValue: if(unitless($mobile), #{$mobile}px, $mobile);
@if (exists($pc)) {
// exists:sass中判断是否有值
@include pc {
// 例如:@include res(font-size, 12, 20); => 转为:font-size: 20px;
#{$property}: $pcValue;
}
}
@if (exists($mobile)) {
@include mb {
#{$property}: $mobileValue;
}
@include mb-container {
#{$property}: $mobileContainerValue;
}
}
}
- 在 低代码编辑器容器中设置
scss
.edit-render {
// 渲染区:容器查询
container-type: inline-size;
}
- 使用封装的响应式协议
scss
@include b('empty') {
width: 100%;
text-align: center;
margin: 0 auto;
.image {
object-fit: cover;
display: block;
margin: 0 auto;
@include res(width, 80, 100);
}
.description {
color: #77787b;
@include res(font-size, 14, 14);
@include res(margin-top, 16, 16);
}
}