Web版Excel制做过程分享

因为项目须要制做一个Web版本Excel用于表单、报表在线绘制,网上搜了一圈没有找到合适的资源,根据搜到的一些零散信息决定本身动手作一个,本文分享这个制做过程,主要包含表格布局、表头固定、动态调整行高列宽、单元格选中、合并与拆分单元格等功能,供你们交流分享。废话少说先上个效果图以下:css

 

1、技术选型

1. 本例基于Jquery库和Vue框架实现,其中Vue并非必须,仅仅由于项目须要而已,读者只需稍做改造去掉对Vue的依赖便可。html

2. 出于对简单直观的追求,笔者选择基于table元素而不是基于div组合,前端

2、先用table作个Excel表格的样子

原本以为很容易,用框架动态生成一个n行m列的table,并在第一行自动填充ABC...Z等做为列表头,在第一列自动填充123...n等做为行表头,使用Vue框架v-for循环生成tr和td元素便可,不熟悉vue的同窗能够简单了解一下vue中v-for指令,固然也能够用原生js或jquery生成这个table的全部行列单元格,整体布局的思路以下:vue

1. 外层用一个div控制显示区域,让表格在这个区域内显示,超出该区域则滚动:overflow:scrolljquery

2. 内层用table绘制表格,其中第一行和第一列单独绘制,填入表头字母和数字,每一个单元格宽默认100px,tr行高默认28pxc++

 1 <!DOCTYPE html>
 2 <html lang="zh" xmlns:v-bind="http://www.w3.org/1999/xhtml">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Title</title>
 6     <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
 7     <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 8 </head>
 9 <body>
10     <div id="app">
11         <div class="form-frame">
12             <table class="form-table">
13                 <tr class="form-row">
14                     <th class="form-header" width="40px"></th>
15                     <th class="form-header" v-for="col in 26" width="100px">{{String.fromCharCode(col+64)}}</th>
16                 </tr>
17                 <tr class="form-row" v-for="row in 40">
18                     <td class="form-header" width="40px">{{row}}</td>
19                     <td class="form-cell" v-for="col in 26"></td>
20                 </tr>
21                 <tr></tr>
22             </table>
23         </div>
24     </div>
25     <script>
26         var vue = new Vue({ 27  el:'#app'
28  }); 29     </script>
30     <style>
31  .form-frame{
32  width: 700px;
33  height: 350px;
34  margin: 0 auto;
35  overflow:scroll;
36  border: 1px solid red;
37         }
38  .form-table{
39  border-spacing:0;
40         }
41  .form-header{
42  font-weight: normal;
43  text-align: center;
44         }
45  .form-row{
46  height: 28px;
47         }
48  th, td{
49  border-right: 1px solid;
50  border-bottom: 1px solid;
51         }
52  .form-cell{
53  width: 100px;
54         }
55     </style>
56 </body>
57 </html>
View Code

结果以下图所示(红色是外层div的边框,为了调试方便),代码中咱们给每一个单元格设置了100px的宽度,一共生成了27列,按理说他应该把整个table撑开到至少2700px;然而如图所示整个table的宽度并无被撑开,而是自适应了外层div的宽度。这就是咱们今天要解决的第一个问题,table元素td标签宽度设置无效的问题。算法

3、解决table中td元素宽度设置无效的问题

网上有说给table添加table-layout: fixed样式,然而这种方法测试后并没效果;其实解决这个问题很简单,就是给table直接指定一个明确的宽度,不妨咱们先设个2700px看看效果chrome

        .form-table{ border-spacing:0; width: 2700px;/*先指定一个明确的宽度*/
        }

 这时候咱们发现整个table确实变宽了,div出现了横向滚动条(图下图所示),说明刚刚给table设置的2700px确实生效了;这也就是要求咱们给table指定的宽度应该恰好是每一列的宽度之和,若是table宽度指定小了,那么他会从每一列中扣除多余的宽度,若是table宽度指定多了,他会给每一列加上相应的宽度,毕竟他要保证全部列不能超出table也不能填不满table。总之每一个td的实际宽度会受到整个table的宽度影响,并不彻底由td自身的width属性决定。npm

 然而不少时候咱们并不能预判咱们到底有多少列,每一列到底有多宽,所以咱们很难一开始就给table设定一个准确的宽度,解决这个问题的办法也很简单,就是额外添加一个不指定宽度的列,这个列咱们称之为自适应列,有了这一列后,table就不须要指定一个准确的宽度,而是设置一个比预估宽度稍大一些的值,多出的这部分宽度都会由该列自适应,所以咱们修改源码,添加一个自适应列:浏览器

<tr class="form-row">
                    <th class="form-header" width="40px"></th>
                    <th class="form-header" v-for="col in 26" width="100px">{{String.fromCharCode(col+64)}}</th>
                    <th></th><!--自适应列-->
                </tr>
                <tr class="form-row" v-for="row in 40">
                    <td class="form-header" width="40px">{{row}}</td>
                    <td class="form-cell" v-for="col in 26"></td>
                    <td></td><!--自适应列-->
                </tr>

 同时把table宽的设置为3000px:

 .form-table{ border-spacing:0; width: 3000px;/*设置一个稍大的宽度*/
        }

效果以下图所示,最右侧这一列会自动适应多出的宽度,在不调整列宽的状况下这样就OK了。若是要调整列宽请看第五节内容。

关于table中td宽度的更多说明能够参考连接:http://www.cnblogs.com/mqingqing123/p/6163140.html

4、解决table表头固定的问题

本例中table第一行和第一列都属于Excel表格的表头,须要固定不动。网上有解决方案就是使用两个table,一个作表头,一个作表身,这种方案只能解决列表头的问题,若是要同时解决固定行表头和列表头的问题,可能须要三个table,这样的方案会让页面布局变得十分复杂,难以维护,违背咱们简单直观的初衷。

为了让代码尽可能简洁而优雅,有没有基于当前这一个table的办法呢?固然有,网上已经有人介绍过了,那就是将表头采用relative布局,并经过滚轮事件实时更新表头位置:

1. 给第一行表头添加col-header类,给第一列表头添加row-header类,注意:左上角第一个单元格,既是行表头又是列表头

2. 给全部表头单元格设置样式position: relative,并加上底色(表头得看上去像表头的样子)

3. 监听外层div的滚动事件,实时更新列表头的top值为div的scrollTop值,实时更新行表头的left值为div的scrollLeft值,这一步是关键,主要是保持表头的位置,让表头单元格不随着滚动条的滚动而移动。

html代码:

<tr class="form-row">
                    <th class="form-header col-header row-header" width="40px"></th>
                    <th class="form-header col-header" v-for="col in 26" width="100px">{{String.fromCharCode(col+64)}}</th>
                    <th class="form-header col-header"></th>
                </tr>
                <tr class="form-row" v-for="row in 40">
                    <td class="form-header row-header" width="40px">{{row}}</td>
                    <td class="form-cell" v-for="col in 26"></td>
                    <td></td>
                </tr>

css代码:

 .form-header{ font-weight: normal; text-align: center; position: relative;/*设置相对定位*/ background-color: #f7f7f7;/*表头背景色*/
        }

js代码:

$(".form-frame").scroll(function () { $(".col-header").css('top',$(".form-frame").scrollTop()); //实时更新第一行表头的位置,让他不随滚动条滚动而移动 $(".row-header").css('left',$(".form-frame").scrollLeft()); //实时更新第一列表头的位置,让他不随滚动条滚动而移动
})

效果以下图所示,能够实现内容滚动,而横竖表头都不动。

 然而如图所示还有一点问题,就是左上角压盖的问题,这个问题也很简单,咱们只须要把第一行第一列的单元的z-index值设置为1便可,让它始终在其余单元格上面就不会被覆盖了。

 这种方案在chrome上表现十分完美,可是在ie会出现表头闪烁的问题,估计与ie触发滚动事件的频率或机制有关,若是对浏览器没有要求,那么十分推荐这种方式,若是没法容忍ie上的表头闪烁问题,那就只能另想办法了。

5、table动态调整列宽和行高

所谓动态调整列宽和行高,就是经过鼠标拖动表头单元格之间的分割线来实现行高和列宽的调整,能够参考这一片文章:https://blog.csdn.net/zanychou/article/details/46988529,基本思路以下:

1. 监听表头单元格的mousedown、mousemove和mouseup事件,

2. 经过鼠标坐标位置来判断是否处于可拖动区域,能够定义表头单元格分割线及其左右(上下)两边5px范围内为可拖动区域,以下图所示,

3. mousedown记录要调整的td及其原始宽度和坐标,mousemove实时计算新的宽度,mouseup结束拖动,

在上述参考文章的基础上,笔者稍作了些调整,基本思路不变,调整点有以下几项:

1. 让行表头和列表头都能动态调整列宽,可是第一列和第一行固定不动(表头自己的宽高要固定)

2. 两个单元格分割线的的两侧均可以拖动(原文只能拖动分割线的左侧区域)

3. 让整个table的宽度随着列宽的调整一块儿调整(保证其余列宽度不变,此处衔接上面第三节留下的疑问,缘由参考上面的第三节)

4. 监听了整个table的mousemove和mouseup事件,让鼠标拖动操做不至于必须保持在表头单元格上,这样交互体验会更好。

关键代码以下黄色背景标记:

html:监听相关事件,同时为了让调整行高列宽的js可以生效,必须把第一行的单元格的宽度和第一列单元格的高度定义在html中,而不是在css中,以下:

<table class="form-table" @mousemove="table_mousemove" @mouseup="table_mouseup">
    <tr class="form-row">
        <th class="form-header col-header row-header all-header"></th>
        <th class="form-header col-header" v-for="col in 26" width="100px" @mousedown="col_header_mousedown" @mousemove="col_header_mousemove"> {{String.fromCharCode(col+64)}} </th>
        <th class="col-header"></th>
    </tr>
    <tr class="form-row" v-for="row in 40">
        <td class="form-header row-header" height="28px" @mousedown="row_header_mousedown" @mousemove="row_header_mousemove"> {{row}} </td>
        <td class="form-cell" v-for="col in 26"></td>
        <td></td>
    </tr>
</table>

css:为了保证第一行第一列即表头自己的行高列宽不变,所以把第一个单元格的高宽放到css中而不是放在html,这样动态调整行高列宽的js就对第一行第一列不生效了。

/*第一行第一列单元格*/ .all-header{ z-index: 1; height: 28px; width: 40px;
}

js代码:仅列了列宽的动态调整,行高的逻辑与之类似

var vue = new Vue({ el:'#app', data:{ //记录当前正在调整行高和列宽表头单元格
 resize_header:{ row_header: null, col_header: null }, }, //初始化固定表头
    mounted:function(){ $(".form-frame").scroll(function () { $(".col-header").css('top',$(".form-frame").scrollTop()); $(".row-header").css('left',$(".form-frame").scrollLeft()); }) }, methods:{ //鼠标点击列表头(第一行)
        col_header_mousedown:function (event) { //判断有效区域,单元格分割线先后5个像素
            if (event.offsetX >= event.target.offsetWidth - 5 && event.buttons == 1) { this.resize_header.col_header = event.target; //当前单元格
            } else if (event.offsetX < 5 && event.buttons == 1) { this.resize_header.col_header = $(event.target).prev()[0];//左侧单元格
 } //记录表头原始属性
            if(this.resize_header.col_header != null){ this.resize_header.col_header.oldX = event.clientX; this.resize_header.col_header.oldWidth = this.resize_header.col_header.offsetWidth; $(".form-table")[0].oldWidth = $(".form-table").width();//记录整表宽度
 } }, //鼠标在第一行移动,改变光标符号
        col_header_mousemove:function (event) { //改变光标样式
            if (event.offsetX >= event.target.offsetWidth - 5 || event.offsetX < 5) event.target.style.cursor = 'col-resize'; else event.target.style.cursor = 'default'; }, //鼠标拖动中,实时计算新的宽高
        table_mousemove:function (event) { //调整列宽
            var c_header = this.resize_header.col_header; if(c_header != null){ if(c_header.oldWidth + event.clientX - c_header.oldX > 10){ c_header.width = c_header.oldWidth + event.clientX - c_header.oldX; c_header.style.width = c_header.width; $(".form-table").width($(".form-table")[0].oldWidth + event.clientX - c_header.oldX);//同步调整表格宽度
 } } }, //表格鼠标抬起,清空记录
        table_mouseup:function (event) { this.resize_header.col_header = null; } } });

效果图以下:

 

6、table中td单元格单选和多选(鼠标拖动选中或叫拉框选中)

单元格的选中是一个很是重要的功能,不少Excel其余功能都是针对当前选中单元格的,这里咱们主要讨论经过鼠标交互的单元格单选和多选,所以咱们须要给每一个单元格监听三个鼠标事件:mousedown、mouseover和mouseup。

  1. 经过mousedown实现单选和肯定当前激活单元格,而不使用mouseclick,由于click须要等鼠标按键抬起才会触发,而咱们要求鼠标点下当即触发(能够参考MS Excel的交互机制);
  2. 使用mouseover实现鼠标拖动时触发区域多选,而不使用mousemove,由于mouseover只会在一个单元格内触发一次,而mousemove在鼠标移动过程当中会不停的触发,影响性能并且没有必要;
  3. mouseup中作状态清除工做。

6.1 样式分析

首先咱们来分析一下选中区域的样式,有一个焦点单元格背景为白色,其余选中单元格背景为浅绿色,最外围单元格存在绿色加粗边框线:

经过简单的分析咱们能够用如下6个class来拆分这些样式,最后将这些class分别叠加到相应的单元格上便可:

  • .cell-select: 浅绿色背景,应用到全部选中单元格上,后面能够经过该class一次性获取全部选中单元格
  • .cell-focus: 白色背景,应用到焦点单元格上,覆盖第一个class
  • .cell-select-top: 带有上边框,应用在最上面的单元格上
  • .cell-select-right: 带有右边框,应用在最右边的单元格
  • .cell-select-bottom: 带有下边框,应用在最下边单元格
  • .cell-select-left: 带有左边框,应用在最左边单元格

将以上6个class用到对应的单元格上便可呈现上图所示的选中效果,例如上图中第一个单元格同时拥有:.cell-select、.cell-focus、.cell-select-left、.cell-select-top四个样式。

6.2 位置分析

所谓位置分析即,根据鼠标点击和移动的位置提取出全部选中的单元格,而后才能给他们设置相应的样式,为了方便处理位置信息,咱们给全部单元格添加一个row和col属性,用于标记该单元格的行列坐标位置:

<table class="form-table" @mousemove="table_mousemove" @mouseup="table_mouseup">
    <tr class="form-row">
        <th class="form-header col-header row-header all-header"></th>
        <th class="form-header col-header" v-for="col in 26" width="100px" @mousedown="col_header_mousedown" @mousemove="col_header_mousemove"> {{String.fromCharCode(col+64)}} </th>
        <th class="col-header"></th>
    </tr>
    <tr class="form-row" v-for="row in 40">
        <td class="form-header row-header" height="28px" @mousedown="row_header_mousedown" @mousemove="row_header_mousemove"> {{row}} </td>
        <td class="form-cell" v-for="col in 26" v-bind:row="row" v-bind:col="col" @mousedown="cell_mousedown" @mouseover="cell_mousemove" @mouseup="cell_mouseup"></td>
        <td></td>
    </tr>
</table>

1. 监听全部单元格mousedown事件,触发该事件的单元格即为起始单元格,也是焦点单元格,记录到全局变量focus_td中,代码略

2. 监听全部单元格mouseover事件,触发该事件的单元格即为当前单元格起始单元格当前单元格之间的位置关系根据鼠标移动方向不一样有如下四种:

不管是哪种方向,我都转换为第一种类型,即转换为经过左上角坐标和右下角坐标定位的方式,设 fromTd 为起始单元格,toTd为当前单元格,那么设置选中区域核心代码以下:

js代码(在mouseover事件中调用):

//选中指定两个单元格之间的全部单元格
region_select:function (fromTd, toTd) { //清除以前的选区
    this.remove_select(); //获取两个单元格的坐标数据
    var f_row = Number(fromTd.attr("row")); var f_col = Number(fromTd.attr("col")); var t_row = Number(toTd.attr("row")); var t_col = Number(toTd.attr("col")); //提取左上角坐标和右下角坐标
    var ltRow = f_row <= t_row ? f_row : t_row; //左上角对应行
    var ltCol = f_col <= t_col ? f_col : t_col; //左上角对应列
    var rbRow = f_row >= t_row ? f_row : t_row; //右下角对应行
    var rbCol = f_col >= t_col ? f_col : t_col; //右上角对应列

    //根据坐标范围遍历单元格,设置相应的样式
    var table = fromTd[0].offsetParent; for(var r=ltRow; r<=rbRow; r++){ for(var c=ltCol; c<=rbCol; c++){ table.rows[r].cells[c].classList.add("cell-select"); if(r==ltRow) table.rows[r].cells[c].classList.add("cell-select-top"); if(r==rbRow) table.rows[r].cells[c].classList.add("cell-select-bottom"); if(c==ltCol) table.rows[r].cells[c].classList.add("cell-select-left"); if(c==rbCol) table.rows[r].cells[c].classList.add("cell-select-right"); } } }, //清除全部选中效果
remove_select:function () { $(".cell-select").removeClass("cell-select"); $(".cell-select-top").removeClass("cell-select-top"); $(".cell-select-right").removeClass("cell-select-right"); $(".cell-select-bottom").removeClass("cell-select-bottom"); $(".cell-select-left").removeClass("cell-select-left"); }

具体的事件监听及其处理逻辑代码略 

7、table中合并单元格与拆分单元格

在上一步完成后,就能够开始作单元格合并与拆分了,即将当前选中区域的全部单元格合并,或将已经合并的单元格拆分。

7.1 合并单元格

table标签自己就支持合并单元格,这也是一开始技术选型使用table而不是div的好处之一,具体方法看图分析以下:

1. 选区中第一个单元称之为扩展单元格,本例中只须要设置该单元格的colspan=3,rowspan=4,便可达到扩展的效果,即合并单元格效果

2. 选区中其余单元格称之为被合并单元格,被合并单元格若是不作任何处理,会被扩展单元格挤开而向两边顺延致使整个table不规则;若是直接把这些被合并的单元格remove掉,那么后面作拆分单元格的时候又须要从新create出来;所以最好的处理办法是将他们设置为display:none,拆分单元格的时候去掉display样式便可。

3. 为了后面作拆分单元格更加方便,咱们须要把这一次合并的单元作一个统一的标记,例如统一添加一个merged-by属性,属性值为扩展单元格的行列坐标。

//合并当前选中的全部单元格
merge:function () { var first = $(".cell-select:first"); var last = $(".cell-select:last"); if(!first.is(last)){ var ltRow = Number(first.attr('row')); var ltCol = Number(first.attr('col')); var rbRow = Number(last.attr('row')); var rbCol = Number(last.attr('col')); var rest_cells = $(".cell-select:gt(0)"); rest_cells.addClass("cell-removed"); //即display:none
        rest_cells.attr("merged-by", ltRow + '_' + ltCol); //添加merged-by标记,方便后期拆分单元格
        first.attr("colspan", rbCol - ltCol + 1); first.attr("rowspan", rbRow - ltRow + 1); this.region_select(first, first); //选中合并后的单元格
 } }

7.2 拆分单元格

拆分单元格实际上就是合并单元格的逆过程:

1. 把当前要拆分单元格的colspan和rowspan属性去掉

2. 把以前被合并的单元格根据merged-by属性一次性选出来(此处是关键),去掉display属性,去掉merged-by标记

//取消合并单元
demerge:function () { var colspan = Number(this.focus_td.attr("colspan")); var rowspan = Number(this.focus_td.attr("rowspan")); if(colspan > 1 || rowspan > 1) { //去掉colspan、rowspan
        this.focus_td.removeAttr("colspan"); this.focus_td.removeAttr("rowspan"); //根据merged-by找到被合并的单元格
        var flagAttr = this.focus_td.attr("row") + '_' + this.focus_td.attr("col") var merged_cells = $(".cell-removed[merged-by="+flagAttr+"]"); merged_cells.removeClass("cell-removed"); //去掉display:none
        merged_cells.removeAttr("merged-by"); //去掉merged-by标记
        this.region_select(this.focus_td, merged_cells.last()); //选中拆分后的区域
 } }

效果如图所示

 

8、针对第六节中单元格选中的重构

当有了合并单元格后,第六节中的单元格选中功能就存在bug了,可能存在以下状况:

所以一旦选区中包含了合并的单元格,那么整个选区范围的计算就不同了,此时咱们须要把全部相关的合并单元格都要归入到选区范围计算中来。若是整个表格中存在多个合并单元格,状况会变得更加复杂:每次归入一个合并单元格后,选区范围可能会扩大,选区范围扩大后可能会再与另外一个合并单元格相交,这时就须要继续扩大选区,直到再没有与其余合并单元格相交为止,已经变成一个递归问题了,考虑性能问题,仍是转换为循环问题处理。

本文采用边缘扫描法来实现循环扩展选区:

    1. 给定一个初始的左上角和右下角单元格坐标;

    2. 扫描初始坐标构成的矩形边缘单元格,寻找是否存在合并单元格(有merged-by属性或colspan属性);

        2.1. 若是找到了合并单元格,获取该合并单元格左上角和右下角坐标,并与初始坐标范围对比;

            2.1.1 若是超出了初始坐标,则扩大初始坐标至能够包含该合并单元格,返回到第1步;

            2.1.2 没有超出坐标则继续;

        2.2. 若是没有找到则继续;

    3. 最终获得的坐标范围即为当前完整的选区。

下图说明了整个边缘扫描算法的选区扩大过程:

修改以前的region_select方法以下:

//选中指定两个单元格之间的全部单元格
region_select:function (fromTd, toTd) { this.remove_select(); var f_row = Number(fromTd.attr("row")); var f_col = Number(fromTd.attr("col")); var t_row = Number(toTd.attr("row")); var t_col = Number(toTd.attr("col")); var ltRow = f_row <= t_row ? f_row : t_row; //左上角对应行
    var ltCol = f_col <= t_col ? f_col : t_col; //左上角对应列
    var rbRow = f_row >= t_row ? f_row : t_row; //右下角对应行
    var rbCol = f_col >= t_col ? f_col : t_col; //右上角对应列

    var table = fromTd[0].offsetParent; //从这里开始进行边缘扫描扩展选区
    do { var extend = false; //标记是否扩展了选区
        outer:for (var r = ltRow; r <= rbRow; r++) { inner:for (var c = ltCol; c <= rbCol; c++) { if (r == ltRow || r == rbRow || c == ltCol || c == rbCol) {//只取边缘单元格
                    var edgeTd = $(table.rows[r].cells[c]); var mergeTd = null; if (edgeTd[0].hasAttribute("merged-by")) {  //判断是否合并单元格
                        var cordinate = edgeTd.attr("merged-by").split("_"); var rowNum = Number(cordinate[0]); var colNum = Number(cordinate[1]); mergeTd = $(table.rows[rowNum].cells[colNum]); } else if (edgeTd[0].hasAttribute("colspan")) {  //判断是否合并单元格
                        mergeTd = edgeTd; } if (mergeTd != null) { //若是是合并单元格 
                        var m_ltRow = Number(mergeTd.attr("row")); var m_ltCol = Number(mergeTd.attr("col")); var m_rbRow = m_ltRow + Number(mergeTd.attr("rowspan")) - 1; var m_rbCol = m_ltCol + Number(mergeTd.attr("colspan")) - 1; //将合并单元格坐标范围与初始范围对比,一旦超出则扩展初始范围,标记extend=true
                        if (m_ltRow < ltRow) { ltRow = m_ltRow; extend = true; } if (m_ltCol < ltCol) { ltCol = m_ltCol; extend = true; } if (m_rbRow > rbRow) { rbRow = m_rbRow; extend = true; } if (m_rbCol > rbCol) { rbCol = m_rbCol; extend = true; } } if(extend)break outer; //若是范围扩展了,则从新扫描新的边缘
 } } } }while(extend); //直到再也不扩展,边缘扫描结束

    //给选区单元格添加样式
    for(var r=ltRow; r<=rbRow; r++){ for(var c=ltCol; c<=rbCol; c++){ //先肯定该单元格要添加的样式
            var classArray = ["cell-select"]; if(r==ltRow) classArray.push("cell-select-top"); if(r==rbRow) classArray.push("cell-select-bottom"); if(c==ltCol) classArray.push("cell-select-left"); if(c==rbCol) classArray.push("cell-select-right"); //若是该单元格是个被合并的单元格,则将其样式应用到合并它的单元格上
            var tmpTd = $(table.rows[r].cells[c]); if (tmpTd[0].hasAttribute("merged-by")) { var cordinate = tmpTd.attr("merged-by").split("_"); var rowNum = Number(cordinate[0]); var colNum = Number(cordinate[1]); var tmpTd = $(table.rows[rowNum].cells[colNum]); } for(var i=0; i<classArray.length; i++){ tmpTd.addClass(classArray[i]); } } } }
View Code

效果图以下,图中“起”表示鼠标开始位置,“止”表示鼠标结束位置,因为受图中三个合并单元格的影响,整个选取扩大至恰好能包含三个合并单元格的范围:

九 其余相关功能

前面主要探讨了:表格布局,固定表头、动态调整行高列宽,鼠标区域选中,合并与拆分单元格等功能的实现原理,这些仅仅是Excel中最基本的交互操做,还有其余一些基本功能能够在此基础上延伸,例如整行整列选中、文字对齐、字体字号、边框设置、背景设置等等,大多数状况下只须要经过.cell-select样式获取到当前选中的单元格,而后应用相应的样式便可,所以单元格选中是基础功能中的基础功能。

最后,笔者非专业前端开发出身,欢迎你们批评指正。

相关文章
相关标签/搜索