今天我们来实现缩放功能,参考同类产品的布局,一般是放在右下角,简单起见,我们就使用普通的下拉框了。
- 首先配置一个默认的缩放比例,
src/core/data_proxy.js
const defaultSettings = {
locale: 'zh',
mode: 'edit', // edit | read
scale: 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;
}
- 调用,底部状态栏只在入口文件中使用,我们也把相关调用也放到该文件就行
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();
}
}
- 样式(我使用了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;
}
}
}
- 应用缩放
因为表格使用 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);
}
}
...
}
这时候,缩放已经可以了,但是点击时,发现选中框与单元格匹配不上。这是因为我们只在表格上进行了绽放,下面我们对选择与编辑进行相应的缩放处理。
- 选区
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);
...
}
}
到此,结束。