基于Canvas+React的TabControl选项卡、页签组件

4 篇文章 0 订阅
3 篇文章 0 订阅

        语言:JavaScript、TypeScript、ES6语法;

        框架React

        技术:Canvas画布、React条件渲染、可视化操控溢出控制;

先上图,看看效果:

再来谈谈出发点及该组件的功能。

目的:

1、为了练手;

2、为了学习;

3、为了替代Antd的TabPane(高度算不准,宽度不到位,占用间距太大);

4、更是个人框架的一个重要组成部分;

组件功能特性:

1、组件成员:TabControl、TabPage

2、由于采用Canvas做页签头,样式属性化;

3、页签支持图标、关闭控制、默认、选中、悬浮、激活状态下的背景色、前景色;

4、增加溢出按钮,可以快速浏览溢出的页签。避免页签数量过多时,左右来回切换,却始终找不到用户想要的页签;

核心技术点:

1、Canvas画布,绘制半闭合,或者全闭合的圆角矩形(也可以在四周,补充扇形。作者在此取消,补了几个模糊的点,肉眼看上去是圆形的);

2、页签溢出、左右偏移算法;

3、页签选中、关闭的state状态控制(重要知识点:React.children、React.cloneElement() 的使用 )

上部分核心代码:

1、Canvas绘制顶部圆角的边框

  private DrawItemBackgroundRound(poBounds: Rectangle, poColor: any, plSelected: boolean) {
    let loPoints = new RayningArray<Point>();
    if (this.TabControl.TabStyle === 'Round') {
      // 顶部圆角
      loPoints.Add(new Point(poBounds.X, poBounds.Bottom));
      loPoints.Add(new Point(poBounds.X, poBounds.Y + 4));
      loPoints.Add(new Point(poBounds.X + 1, poBounds.Y + 1.5));
      loPoints.Add(new Point(poBounds.X + 1, poBounds.Y + 1));
      loPoints.Add(new Point(poBounds.X + 3, poBounds.Y + 0.5));
      loPoints.Add(new Point(poBounds.X + 4, poBounds.Y));

      loPoints.Add(new Point(poBounds.Right - 4, poBounds.Y));
      loPoints.Add(new Point(poBounds.Right - 3, poBounds.Y + 0.5));
      loPoints.Add(new Point(poBounds.Right - 1, poBounds.Y + 1));
      loPoints.Add(new Point(poBounds.Right - 1, poBounds.Y + 1.5));
      loPoints.Add(new Point(poBounds.Right, poBounds.Y + 4));

      loPoints.Add(new Point(poBounds.Right, poBounds.Bottom));
    } else {
      // 矩形
      loPoints.Add(new Point(poBounds.X, poBounds.Bottom));
      loPoints.Add(new Point(poBounds.X, poBounds.Y));
      loPoints.Add(new Point(poBounds.Right, poBounds.Y));
      loPoints.Add(new Point(poBounds.Right, poBounds.Bottom));
    }

    this.Graphics.FillPath(poColor, loPoints);
    this.Graphics.DrawPath(new Pen(this.TabControl.TabBorderColor, 1), loPoints);
  }

2、计算页签溢出、偏移量

 /**
   * 滚动选中页签至可视范围内。
   * @param piOffset
   */
  private ScrollPageItems(piOffset: number) {
    let loSelectedPage: TabPageItem | null = null;
    for (let liIndex = 0; liIndex < this._PageItems.Count; liIndex++) {
      let loPage = this._PageItems[liIndex];
      if (loPage.Key === this.TabControl.SelectedKey) {
        loSelectedPage = loPage;
        break;
      }
    }

    if (!loSelectedPage) {
      return;
    }

    if (this._PageItems[0].Bounds.X + piOffset > 5.5) {
      // 第一个页签,不能偏移越界
      piOffset = this._PageItems[0].Bounds.X - 5.5;
      this._ScrollOffset = piOffset;
    }

    loSelectedPage.Bounds.Offset(piOffset, 0);
    loSelectedPage.ImageBounds.Offset(piOffset, 0);
    loSelectedPage.CloseBounds.Offset(piOffset, 0);
    loSelectedPage.Displayed = true;
    if (this._TabPageItemMore.Displayed) {
      // 定位溢出按钮
      for (let liIndex = this._PageItems.Count - 1; liIndex >= 0; liIndex--) {
        let loTempPage = this._PageItems[liIndex];
        if (loTempPage.Bounds.Right < this.Width) {
          this._TabPageItemMore.Bounds.X = loTempPage.Bounds.Right + 3;
          break;
        }
      }
    }

    for (let liIndex = 0; liIndex < this._PageItems.Count; liIndex++) {
      let loTempPage = this._PageItems[liIndex];
      if (loTempPage.Key === loSelectedPage.Key) {
        loTempPage.Displayed = true;
        continue;
      }

      // 执行偏移
      loTempPage.Bounds.Offset(piOffset, 0);
      loTempPage.ImageBounds.Offset(piOffset, 0);
      loTempPage.CloseBounds.Offset(piOffset, 0);
      loTempPage.Displayed = false;
      if (
        loTempPage.Bounds.Right > 5.5 &&
        loTempPage.Bounds.Right <= this._TabPageItemMore.Bounds.X
      ) {
        loTempPage.Displayed = true;
      }
    }
  }

  /**
   * 计算页签偏移量。
   */
  private GetScrollOffset() {
    let liOffset = 0;
    let loSelectedPage: TabPageItem | null = null;
    for (let liIndex = 0; liIndex < this._PageItems.Count; liIndex++) {
      let loPage = this._PageItems[liIndex];
      if (loPage.Key === this.TabControl.SelectedKey) {
        loSelectedPage = loPage;
        break;
      }
    }

    if (!loSelectedPage) {
      return liOffset;
    }

    // 以选中页签做参照物,测量页签偏移量
    if (loSelectedPage.Bounds.X < 5.5) {
      // 向左偏移
      liOffset = 5.5 - loSelectedPage.Bounds.X;
    } else {
      // 向右偏移
      if (this._TabPageItemMore.Displayed && loSelectedPage.Bounds.Right + 40 > this.Width) {
        liOffset = this.Width - loSelectedPage.Bounds.Right;
        liOffset -= 40;
      } else if (loSelectedPage.Bounds.Right > this.Width) {
        liOffset = this.Width - loSelectedPage.Bounds.Right;
      }
    }

    if (liOffset !== 0) {
      this._ScrollOffset = liOffset;
    }
    return liOffset;
  }

3、React渲染子组件。渲染规则,如果是选中的页签,就可见;否则,不可见。

this.SelectedKey,选中的页签Key;

this.TabPages,基于state状态控制的Page页签集;

Displayed,传递个TabPage页签,由页签自身校验可见性。

TabControl渲染规则:

  render() {
    const loResult = (
      <div style={this.GetStyle()}>
        <TabHeader TabControl={this}></TabHeader>
        <div style={{ padding: '2px', width: '100%', height: `calc(100% - 30px)` }}>
          {React.Children.map(this.Pages, (loTempPage: any) => {
            if (loTempPage.props.Key === this.SelectedKey) {
              return React.cloneElement(loTempPage, { Displayed: true });
            } else {
              return React.cloneElement(loTempPage, { Displayed: false });
            }
          })}
        </div>
      </div>
    );

    return loResult;
  }
TabPage页签渲染;

  render() {
    return (
      <div style={{ width: '100%', height: '100%', display: `${this.Displayed}` }}>
        {this.props.children}
      </div>
    );
  }

本人对GDI+、Canvas相当痴迷,并小有成就。欢迎咨询、洽谈,相互学习、互相进步。 

基于Canvas+React的高性能Table表格_睿凝奇兵的博客-CSDN博客

基于 Canvas 绘制的连续曲线_睿凝奇兵的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值