layout布局_Angular 组件库 NG-NEST 源码解析:Layout 栅格布局

前言

NG-NEST介绍

今天我们看一下 Layout 栅格布局 是如何实现的

0b8c2319e204bf063f5da45815ddfd8f.gif

功能分析

栅格布局是基于行和列(24栅格)来定义内容区域的,提供了以下的功能:

  • 基础布局
  • 分栏间隔
  • 分栏偏移
  • 对齐方式
  • 响应布局
  • 响应隐藏

代码分析

先看是如何使用的:

<x-row>
  <x-col span="12">col-12</x-col>
  <x-col span="12">col-12</x-col>
</x-row>

x-row 用来定义行,x-col 用来定义列, span 属性用来设置每列的占比 ,涉及到2个组件:

lib/ng-nest/ui/layout
├── style
├── col.component.html         
├── col.component.ts                   列组件
├── row.component.html 
├── row.component.ts                   行组件
├── layout.property.ts                 参数定义
└── ......

我们先看 x-row 行组件 row.component.ts 代码是如何定义的:

@Component({
  selector: `${XRowPrefix}`,
  template: '<ng-content></ng-content>',
  styleUrls: ['./row.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class XRowComponent extends XRowProperty implements OnInit {
  // 当有设置水平或垂直排列方式时添加 x-flex 布局样式
  @HostBinding(`class.x-flex`) get getFlex() {
    return this.justify || this.align ? true : false;
  }

  constructor(private renderer: Renderer2, private elementRef: ElementRef) {
    super();
    this.renderer.addClass(this.elementRef.nativeElement, XRowPrefix);
  }

  ngOnInit() {
    this.setSpace();
    this.setJustify();
    this.setAlign();
  }

  // 设置行的左右外边距
  setSpace() {
    if (!this.space) return;
    this.renderer.setStyle(this.elementRef.nativeElement, 'margin-left', `-${Number(this.space) / 2}rem`);
    this.renderer.setStyle(this.elementRef.nativeElement, 'margin-right', `-${Number(this.space) / 2}rem`);
  }

  // 设置行中列的水平排列方式
  setJustify() {
    if (!this.justify) return;
    this.renderer.addClass(this.elementRef.nativeElement, `x-justify-${this.justify}`);
  }

  // 设置行中列的垂直排列方式
  setAlign() {
    if (!this.align) return;
    this.renderer.addClass(this.elementRef.nativeElement, `x-align-${this.align}`);
  }
}

对应在 layout.property.ts 中的参数定义:

/**
 * Row Property
 */
@Component({ template: '' })
export class XRowProperty extends XProperty {
  /**
   * 列间隔,rem
   */
  @Input() @XInputNumber() space: XNumber;
  /**
   * flex 布局下的水平排列方式
   */
  @Input() justify: XJustify;
  /**
   * flex 布局下的垂直排列方式
   */
  @Input() align: XAlign;
}

列间距、flex 布局下列的排列方式都是通过 x-row 组件设置,接下来我们继续看 x-col 列组件都做了什么事情:

打开 col.component.ts 文件:

@Component({
  selector: `${XColPrefix}`,
  template: '<ng-content></ng-content>',
  styleUrls: ['./col.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class XColComponent extends XColProperty implements OnInit {
  // 如果设置了响应布局,默认添加 x-col-24 样式,占满24格 
  @HostBinding(`class.x-col-24`) get getFlex() {
    return this.xs || this.sm || this.md || this.lg || this.xl || this.span == 24 ? true : false;
  }

  constructor(
    @Optional() @Host() public rowComponent: XRowComponent, 
    private renderer: Renderer2, 
    private elementRef: ElementRef) {
    super();
    this.renderer.addClass(this.elementRef.nativeElement, XColPrefix);
  }

  ngOnInit() {
    this.setSpan();
    this.setOffset();
    this.setSpace();
    this.setLayout();
    this.setInherit();
  }

  // 设置每列的占比样式
  setSpan() {
    if (!this.span) return;
    this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-${this.span}`);
  }
  
  // 设置每列的偏移样式
  setOffset() {
    if (!this.offset) return;
    this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-offset-${this.offset}`);
  }
  
  // 设置列与列之间的间距,来源于行组件的 space 参数
  setSpace() {
    if (!this.rowComponent?.space) return;
    this.renderer.setStyle(this.elementRef.nativeElement, 'padding-left', `${Number(this.rowComponent.space) / 2}rem`);
    this.renderer.setStyle(this.elementRef.nativeElement, 'padding-right', `${Number(this.rowComponent.space) / 2}rem`);
  }

  // 设置响应布局尺寸
  setLayout() {
    if (this.xs) {
      this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-xs-${this.xs}`);
    }
    if (this.sm) {
      this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-sm-${this.sm}`);
    }
    if (this.md) {
      this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-md-${this.md}`);
    }
    if (this.lg) {
      this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-lg-${this.lg}`);
    }
    if (this.xl) {
      this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-xl-${this.xl}`);
    }
  }

  // 设置宽度默认属性
  setInherit() {
    if (!this.inherit) return;
    this.renderer.addClass(this.elementRef.nativeElement, `${XColPrefix}-inherit`);
  }
}

对应在 layout.property.ts 中的参数定义:

/**
 * Col Property
 */
@Component({ template: '' })
export class XColProperty extends XProperty {
  /**
   * 24栅格布局,列占的宽度
   */
  @Input() @XInputNumber() span: XNumber;
  /**
   * 栅格左侧的间隔格数
   */
  @Input() @XInputNumber() offset: XNumber;
  /**
   * <768px
   */
  @Input() @XInputNumber() xs: XNumber;
  /**
   * ≥768px
   */
  @Input() @XInputNumber() sm: XNumber;
  /**
   * ≥992px
   */
  @Input() @XInputNumber() md: XNumber;
  /**
   * ≥1200px
   */
  @Input() @XInputNumber() lg: XNumber;
  /**
   * ≥1920px
   */
  @Input() @XInputNumber() xl: XNumber;
  /**
   * 默认样式
   */
  @Input() @XInputBoolean() inherit: XBoolean;
}

x-rowx-col 组件的代码都比较简单,都是根据参数设置指定的样式,列间距通过获取父组件的 space 参数来设置,我们来看一下具体样式是如何写的:

打开 lib/ng-nest/ui/layout/style/mixin.scss 文件

 @mixin row {
  display: block;
  position: relative;
  height: auto;
  &:before,
  &:after {
    content: '';
    display: table;
    clear: both;
  }
  @include flex();
}

@mixin media-screen($key, $value) {
  $col-layout-initial: $--x-col-layout;
  @while $col-layout-initial>0 {
    $col-percentage: 100% * $col-layout-initial / $--x-col-layout;
    @media only screen and #{inspect($value)} {
      &-#{$key}-#{$col-layout-initial} {
        width: $col-percentage;
      }
    }
    $col-layout-initial: $col-layout-initial - 1;
  }
}

@mixin col {
  position: relative;
  float: left;
  display: block;
  width: 100%;
  box-sizing: border-box;
  $col-layout-initial: $--x-col-layout;
  @while $col-layout-initial>0 {
    $col-percentage: 100% * $col-layout-initial / $--x-col-layout;
    &-#{$col-layout-initial} {
      width: $col-percentage;
    }
    &-offset-#{$col-layout-initial} {
      margin-left: $col-percentage;
    }
    $col-layout-initial: $col-layout-initial - 1;
  }
  @each $key, $value in $--x-size-range {
    @include media-screen($key, $value);
  }
  &-inherit {
    width: inherit;
  }
}

@mixin row 里面定义行样式,在里面又包含了一个 flex 混合,这个定义在 lib/ng-nest/ui/style/mixins/flex.scss 中,主要就是 flex 布局的一些通用样式,通过 flex 最终生成的样式如下:

.x-row.x-flex {
  display: flex;
}
.x-row.x-justify-start {
  justify-content: flex-start;
}
.x-row.x-justify-center {
  justify-content: center;
}
.x-row.x-justify-end {
  justify-content: flex-end;
}
.x-row.x-justify-space-between {
  justify-content: space-between;
}
.x-row.x-justify-space-around {
  justify-content: space-around;
}
.x-row.x-align-start {
  align-items: flex-start;
}
.x-row.x-align-center {
  align-items: center;
}
.x-row.x-align-end {
  align-items: flex-end;
}
.x-row.x-direction-column {
  flex-direction: column;
}
.x-row.x-direction-column-reverse {
  flex-direction: column-reverse;
}
.x-row.x-direction-row {
  flex-direction: row;
}
.x-row.x-direction-row-reverse {
  flex-direction: row-reverse;
}

@mixin media-screen 里面定义响应样式,$--x-col-layout 为 24 栅格,通过 @while 循环输出不同尺寸对应 @media 查询样式,最终生成的样式代码如下:

@media only screen and (max-width: 767px) {
  .x-col-xs-24 {
    width: 100%;
  }
}
@media only screen and (max-width: 767px) {
  .x-col-xs-23 {
    width: 95.8333333333%;
  }
}

...

@media only screen and (max-width: 767px) {
  .x-col-xs-1 {
    width: 4.1666666667%;
  }
}
@media only screen and (min-width: 768px) {
  .x-col-sm-24 {
    width: 100%;
  }
}

...

@media only screen and (min-width: 768px) {
  .x-col-sm-1 {
    width: 4.1666666667%;
  }
}
...
@media only screen and (min-width: 992px) {
  .x-col-md-24 {
    width: 100%;
  }
}
...

@mixin col 定义列样式,同样通过 @while 循环输出对应栅格所占用的宽度,此处还包含了偏移的格数,通过 margin-left 来设置,还有一个 @each in 的用来遍历输出定义的媒体查询参数 $--x-size-range

// Layout
$--x-col-layout: 24;

$--x-sm: 768px !default;
$--x-md: 992px !default;
$--x-lg: 1200px !default;
$--x-xl: 1920px !default;

$--x-size-range: (
  'xs': (
    max-width: $--x-sm - 1
  ),
  'sm': (
    min-width: $--x-sm
  ),
  'md': (
    min-width: $--x-md
  ),
  'lg': (
    min-width: $--x-lg
  ),
  'xl': (
    min-width: $--x-xl
  )
);

最终生成的样式如下:

.x-col-24 {
  width: 100%;
}
.x-col-offset-24 {
  margin-left: 100%;
}

...

.x-col-1 {
  width: 4.1666666667%;
}
.x-col-offset-1 {
  margin-left: 4.1666666667%;
}

响应隐藏功能是通过公共的 var-size-hidden 这个混合实现,这个定义在 lib/ng-nest/ui/style/mixins/hidden.scss 中

@mixin var-size-hidden($hidden) {
  @each $key, $value in $hidden {
    [#{$--x-prefix}-hidden-#{$key}] {
      @media only screen and #{inspect($value)} {
        display: none !important;
      }
    }
  }
}

此处就是一个属性样式,通过在具体的 @media 查询样式下隐藏元素,在 lib/ng-nest/ui/style/core/var.scss 中使用:

@include var-size-hidden($--x-size-range-only);

$--x-size-range-only 参数定义如下:

$--x-size-range-only: (
  'xs-only': (
    max-width: $--x-sm - 1
  ),
  'sm-and-up': (
    min-width: $--x-sm
  ),
  'sm-only': (
    min-width: $--x-sm
  )
  and
  (
    max-width: $--x-md - 1
  ),
  'sm-and-down': (
    max-width: $--x-md - 1
  ),
  'md-and-up': (
    min-width: $--x-md
  ),
  'md-only': (
    min-width: $--x-md
  )
  and
  (
    max-width: $--x-lg - 1
  ),
  'md-and-down': (
    max-width: $--x-lg - 1
  ),
  'lg-and-up': (
    min-width: $--x-lg
  ),
  'lg-only': (
    min-width: $--x-lg
  )
  and
  (
    max-width: $--x-xl - 1
  ),
  'lg-and-down': (
    max-width: $--x-xl - 1
  ),
  'xl-only': (
    min-width: $--x-xl
  )
);

生成的最终样式如下:

@media only screen and (max-width: 767px) {
  [x-hidden-xs-only] {
    display: none !important;
  }
}
@media only screen and (min-width: 768px) {
  [x-hidden-sm-and-up] {
    display: none !important;
  }
}
@media only screen and (max-width: 991px) {
  [x-hidden-sm-only] {
    display: none !important;
  }
}
@media only screen and (max-width: 991px) {
  [x-hidden-sm-and-down] {
    display: none !important;
  }
}
@media only screen and (min-width: 992px) {
  [x-hidden-md-and-up] {
    display: none !important;
  }
}
@media only screen and (max-width: 1199px) {
  [x-hidden-md-only] {
    display: none !important;
  }
}
@media only screen and (max-width: 1199px) {
  [x-hidden-md-and-down] {
    display: none !important;
  }
}
@media only screen and (min-width: 1200px) {
  [x-hidden-lg-and-up] {
    display: none !important;
  }
}
@media only screen and (max-width: 1919px) {
  [x-hidden-lg-only] {
    display: none !important;
  }
}
@media only screen and (max-width: 1919px) {
  [x-hidden-lg-and-down] {
    display: none !important;
  }
}
@media only screen and (min-width: 1920px) {
  [x-hidden-xl-only] {
    display: none !important;
  }
}

总结

以上就是 Layout 栅格布局 的源码分析,主要还是一些样式的应用,通过 SCSS 的混合、循环和遍历有效减少了重复的样式代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值