Skip to content

低代码项目样式调研

渲染区样式隔离

在做渲染区的时候,渲染的组件是从物料组件库中引入的,所以需要考虑样式隔离的问题。

  • 影子 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 属性用于定义一个容器,容器内的元素可以根据这个容器的尺寸进行样式调整。它有以下几个取值:

  1. normal:这是默认值,表示该元素不是一个容器查询容器,即不会基于该元素的尺寸对其内部元素进行样式调整
  2. inline-size:只在水平方向建立容器
  3. 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;
    }
  }
}
    1. 在 低代码编辑器容器中设置
scss
.edit-render {
  // 渲染区:容器查询
  container-type: inline-size;
}
    1. 使用封装的响应式协议
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);
  }
}