X-Spreadsheet实现缩放功能

558 阅读2分钟

今天我们来实现缩放功能,参考同类产品的布局,一般是放在右下角,简单起见,我们就使用普通的下拉框了。

  1. 首先配置一个默认的缩放比例,

src/core/data_proxy.js

const defaultSettings = {
  locale: 'zh',
  mode: 'edit', // edit | read
  scale: 1, // 默认缩放比例:1
  ...

配置它主要是为了方便其它组件中引用。

  1. 生成下拉框

src/component/bottombar.js

class BottomBar {
  constructor(...) {
    ...
    this.scale = 1; // 缩放比例
    // 更多下拉菜单
    this.moreEl = new DropdownMore((i) => {
      this.clickSwap2(this.items[i]);
    });
    // 右键菜单
    this.contextmenu = new Contextmenu();
    this.contextmenu.itemClick = deleteFunc;
    // 缩放比例下拉菜单
    this.optionEl = buildOption([0.5, 0.75, 1, 1.2, 1.5, 2, 3, 4], this.scale);
    // 将 新建按钮、更多下拉菜单、右键菜单添加到底部状态栏上
    this.el = h('div', `${cssPrefix}-bottombar`).children(
      this.contextmenu.el,
      (this.menuEl = h('ul', `${cssPrefix}-menu`).child(
        h('li', '').children(
          new Icon('add').on('click', () => {
            addFunc();
          }),
          h('span', '').child(this.moreEl)
        )
      )),
      // 这里下拉框的生成
      (this.scaleEl = h('div', `${cssPrefix}-scale`).child(
        (this.selectEl = h('select', '')
          .attr({ name: 'scale', 'aria-label': 'scale' })
          .on('change', (evt) => {
            const { value } = evt.target;
            this.change(+value);
          })
          .children(...this.optionEl))
      ))
    );
    this.change = () => {};
  }
  ...

  /**
   * 重置缩放比例。
   * 它的作用就是多 sheet 切换时,重置缩放比例。
   * @param {*} scale
   */
  resetScale(scale) {
    this.scale = scale;
    this.optionEl = buildOption([0.5, 0.75, 1, 1.2, 1.5, 2, 3, 4], this.scale);
    this.selectEl.html('').children(...this.optionEl);
  }
}

// 这是 options 的生成函数
function buildOption(scale, value = 1) {
  let arr = [];
  for (let i = 0; i < scale.length; i += 1) {
    if (scale[i] === value) {
      arr.push(
        h('option', 'active')
          .attr('selected', true)
          .val(scale[i])
          .html(scale[i] * 100 + '%')
      );
    } else {
      arr.push(
        h('option', '')
          .val(scale[i])
          .html(scale[i] * 100 + '%')
      );
    }
  }
  return arr;
}
  1. 调用,底部状态栏只在入口文件中使用,我们也把相关调用也放到该文件就行

src/index.js

class Spreadsheet {
  constructor(selectors, options = {}) {
    // 表格底部状态栏
    // 这里通过传递回调函数实现相关功能
    this.bottomBar = this.options.showBottomBar
      ? new BottomBar(
        ...
        (index) => {
          this.currentSheetIndex = index;
          // 为 bottomBar 的 sheet 标签传入点击选中事件
          const d = this.datas[index];
          this.data = d;
          // 重置缩放比例
          this.bottomBar.resetScale(d.settings.scale);
          // 如果使用缩放,单纯靠 resetData 无法达到重绘所有组件的目的,只能换其它方法
          // 重绘布局
          this.sheet.data = d;
          this.sheet.reload();
          // 渲染数据
          this.sheet.resetData(d);
        },
        ...
    )
    : null;

    ...
    // 如果底部状态栏也存在,则加上
    if (this.bottomBar !== null) {
      this.bottomBar.change = (value) => this.changeSheetScale(value);
      rootEl.child(this.bottomBar.el);
    }
  }

  /**
   * 缩放 sheet(工作表)。
   * @param {*} value 缩放比例 0.5~4
   */
  changeSheetScale(value) {
    if ((typeof value === 'number') & (value >= 0.5) && value <= 4) {
      const d = this.datas[this.currentSheetIndex];
      d.settings.scale = value;
      this.sheet.data = d;
      this.sheet.reload();
    }
  }  
  
  1. 样式(我使用了scss,你用less也是一样的)

样式比较简,大家自己写就行。

  &-bottombar {
    justify-content: space-between;
    position: relative;
    border-top: 1px solid #e0e2e4;

    ...

    .#{$css-prefix}-scale {
      padding: 0.785em 1em;

      .active {
        background: #ecf6fd;
        color: #2185d0;
      }
    }
  }
  1. 应用缩放

因为表格使用 canvas ,所以我们在相应的绘制方法中设置缩放比例即可。

src/component/table.js

class Table {
  ...
  /**
   * 渲染表格
   */
  render() {
    // resize canvas
    const { data } = this;
    const {
      rows,
      cols,
      settings: { scale }, // 取出我们的缩放比例
    } = data;
    // fixed width of header
    let fixedIndexWidth = cols.indexWidth;
    // fixed height of header
    let fixedIndexHeight = rows.indexHeight;

    this.draw.resize(data.viewWidth(), data.viewHeight());
    this.clear();
    // 在这里应用
    if (typeof scale === 'number') {
      this.draw.scale(scale, scale);
    }
  }
  ...
}

这时候,缩放已经可以了,但是点击时,发现选中框与单元格匹配不上。这是因为我们只在表格上进行了绽放,下面我们对选择与编辑进行相应的缩放处理。

  1. 选区

src/componet/sheet.js

class Sheet {
  ...
  /**
   * 获得当前 sheet 的宽度、高度和偏移量
   * width: 不包含 index 索引列,即最左侧 1、2、3... 列
   * height: 不包含 title 行,即 最上面 A、B、C... 行
   * left: 左偏移量
   * top: 顶偏移量
   * @returns {{top, left: (number|*), width: number, height: number}}
   */
  getTableOffset() {
    const {
      rows,
      cols,
      settings: { scale },
    } = this.data;
    const { width, height } = this.getRect();
    return {
      width: width - Math.floor(cols.indexWidth * scale),
      height: height - Math.floor(rows.indexHeight * scale),
      left: Math.floor(cols.indexWidth * scale),
      top: Math.floor(rows.indexHeight * scale),
    };
  }
  ...
}

src/core/data_proxy.js

class DataProxy {
  ...
  /**
   * 获得设定选中范围区域。
   * @param {Object} cellRange
   * @returns {Object}
   */
  getRect(cellRange) {
    const { scroll, rows, cols, exceptRowSet } = this;
    const { sri, sci, eri, eci } = cellRange;
    if (sri < 0 && sci < 0) {
      return { left: 0, l: 0, top: 0, t: 0, scroll };
    }
    ...
    return {
      l: left,
      t: top,
      left: left0,
      top: top0,
      height,
      width,
      scroll,
    };
  }
  
  /**
   * 通过鼠标的 x 和 y 坐标,获得当前选中区域。
   * @param {number} x x 坐标
   * @param {number} y y 坐标
   * @returns {*}
   */
  getCellRectByXY(x, y) {
    const {
      scroll,
      merges,
      rows,
      cols,
      settings: { scale },
    } = this;
    let { ri, top, height } = getCellRowByY.call(this, y / scale, scroll.y);
    let { ci, left, width } = getCellColByX.call(this, x / scale, scroll.x);
    ...
  }
 }

到此,结束。