前端合并单元格算法-遁地龙卷风

Posted 遁地龙卷风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端合并单元格算法-遁地龙卷风相关的知识,希望对你有一定的参考价值。

0.要求

  用户点击A单元格作为起始点,点击B单元格最为终止点,要根据A、B两个点算出四个边界值,用来组成出一个矩形。

  

  上图红色为终止点,绿色为起始点。

1.算法

  关键点1:合并单元格是通过rowspan,colspan来实现,意味一个单元格代替多个单元格,算法中计算出的单元格位置需要与在视图中看到的一致,所以和在左上角单元格(在边界值组成的矩形中)在同一行的单元格删除,不再同一行的隐藏。

   

  我们用left、top、right、bottom四个属性来表示一个单元格的位置,上图中被蓝矩形标记的单元格位置为,left:1、top:1、right:3、bottom:2,上述做法主要为了确保left的获取。

  第一步:分别计算A、B两个单元格的left、top、bottom、right,取得四个方向的最值来初步画出一个矩形。关键代码如下

  

getLeft($tr,$td){
        let $tds = $tr.find("td");
        let count = 0;
        for(let i=0;i<$tds.length;i++)
        {
            let $td_temp = $tds.eq(i);
            if($td_temp[0] ==  $td[0])
            {
                return count;
            }
            let colspan = +$td_temp.attr("colspan") || 1;
            count += colspan;
        }
    }

 

  得到单元格在所属行的left属性,right根据left属性加1或colspan属性值,top等同于所属行的位置,bottom根据top属性加上1或rowspan属性值。

  关键点2:当被合并的单元格中有已合并单元格的时候,可能出现某个单元格的四个属性超出已划定矩形的情况,一个边界的变化又会影响到其他的边界。所以需要循环确认最终矩形,直到无单元格位置溢出。

  如下例:

  

  当选择绿色矩形标记的单元格为起始点,红色标记的单元格为终止点,黑色边框是第一步组成的矩形,但蓝色标记单元格的右边界溢出,当调整矩形如下时

  

  黄色标记的单元格bottom属性溢出,最终为:

  

  推算左边界的值“   

get_tdPosLeft($tr,pos){
        let $tds = $tr.find("td");
        let count = 0;
        let colspan = 0;
        for(let i=0;i<$tds.length;i++)
        {
            let $td_temp = $tds.eq(i);

            if(count == pos){
                return count;
            }
            else if(count > pos){
                return count - colspan;
            }
            colspan = +$td_temp.attr("colspan") || 1;
            count += colspan;
        }

    }
//pos的的值等于划定的左边界值,如果单元格的left属性等于pos属性,说明单元格left属性没有溢出,如果大于pos,则左边界往左减1或者减去它的colspan属性值

  推算右边界的值:

get_tdPosRight($tr,pos){
        let $tds = $tr.find("td");
        let count = 0;
        for(let i=0;i<$tds.length;i++)
        {
            let $td_temp = $tds.eq(i);

            let colspan = +$td_temp.attr("colspan") || 1;
            count += colspan;

            if(count == pos){
                return count;
            }
            else if(count > pos){
                return count;
            }
        }
    }
//
//pos的值等于划定的右边界值,如果单元格的right属性等于pos属性,说明单元格right属性没有溢出,如果大于pos,则右边界溢出,返回单元格右边界的值。

  推算上边界:

hasRowCollposed_top($tr,left,right){
        let $tds = $tr.find("td");
        let count = 0;
        for(let i=0;i<$tds.length;i++){
            let $td = $tds.eq(i);

            if($td.hasClass("hidden_rwospan")){

                if(count >= left && count < right){
                    return true;
                }
                else{
                    break;
                }
            }
            count +=  +$td.attr("colspan") || 1; 
        }
        return false;
    }
//这里的left、right等于以推算过后的左边界值和右边界值,如果在left和right之间有td元素含有
//hidden_rwospan类(因合并单元格而添加到元素上),说明上边界溢出,返回true

.hidden_rwospan{
  display:none;
}
如下图:


第二行和第三行的html结构如下:
<tr>
                    <td><span class="tab">  </span>3</td>
                    <td rowspan="2"><span class="tab">  </span>3</td>
                    <td><span class="tab">  </span>3</td>
                    <td><span class="tab">  </span>4</td>
                </tr>
                <tr>
                    <td  ><span class="tab">  </span>3</td>
                    <td class="hidden_rwospan"><span class="tab">  </span>3</td>
                    <td><span class="tab">  </span>3</td>
                    <td><span class="tab">  </span>4</td>
                </tr>


推算下边界:下边界有一个特殊情况,当发现在左右边界内存在拥hidden_rwospan类的单元格时,需要判定是否存在穿透的情况,如下图

黄色标记的单元格穿透了最初划定的边界。
hasRowCollposed_bottom($tr,left,right){

let $tds = $tr.find("td"); let count = 0; for(let i=0;i<$tds.length;i++){ let $td = $tds.eq(i); if(!$td.hasClass("hidden_rwospan")){ if(count >= left && count < right){ let rowspan = +$td.attr("rowspan") || 0; if(rowspan > 1){ return true; } } else if(count>=right){ break; } } else{ // 因为rowspan而隐藏的单元格 if(count >= left && count < right){ //如果隐藏 可能发生上面单元格的rowspan穿透了当前单元格 let right_pos = count + (+$td.attr("colspan") || 1); if (this.isCrossRow_collposed($td,$tr.next(),count,right_pos,left,right)) { return true; } } else if(count>=right){ break; } } count += +$td.attr("colspan") || 1; } return false; }
  //没被隐藏的单元格如果在范围内有单元格拥有rowspan属性且值大于1,则下边界往下推,在范围内拥有
hidden_rwospan的元素,则需要判定是否发生穿透
 

  判定是否发生穿透:


isCrossRow_collposed($td_compare,$tr_next,left_pos,right_pos,left,right){
        //如果在左右边界内有单元格拥有hidden_rwospan类,且left属性和right属性和left_pos、right_pos相同,则bottom需要往下推
       //left_pos 对比单元格左边的位置,right_pos 右边的位置,left 组成矩形的左边界,right 组成矩形右边界
       
        let $tds = $tr_next.find("td");
        let count = 0;
        for(let i=0;i<$tds.length;i++){
            let $td = $tds.eq(i);
            if($td.hasClass("hidden_rwospan")){
                if(count >= left && count < right){
                    if(count == left_pos ){
                        let colspan = left_pos + ( +$td.attr("colspan") || 1);
                        if(colspan == right_pos){
                            return true;
                        }
                    }
                    else if(count > left_pos){
                        break;
                    }
                }
                 
            }
            if(count>=right){
                break;
            }
            count += +$td.attr("colspan") || 1; 
        }
        return  false;
    }

  核心代码(Esl class语法):

getArra_collposed($td_start,$td_end){

        
        //画出最初矩形
        let left = this.getFarLeft($td_start,$td_end);//得到最靠左单元格的left属性值
        let    top =  this.getFarTop($td_start,$td_end);
        let    right =  this.getFarRight($td_start,$td_end);
        let    bottom = this.getFarBottom($td_start,$td_end);
        
        
        let $trs = this.$ele_current.find("tr"),
            $tr_top = $trs.eq(top),
            $tr_bottom = $trs.eq(bottom-1);
        while(true){

            let left_temp = left,
                top_temp = top,
                right_temp = right,
                bottom_temp = bottom;


            for(let i=top;i<bottom;i++){
                let $tr = $trs.eq(i);
                let pos = this.get_tdPosLeft($tr,left);

                if(left_temp > pos){
                    left_temp = pos;
                }
            }

            for(let i=top;i<bottom;i++){//推算右边界
                let $tr = $trs.eq(i);
                let pos = this.get_tdPosRight($tr,right);

                if(right_temp < pos){
                    right_temp = pos;
                }
            }
            
            if(this.hasRowCollposed_top($tr_top,left_temp,right_temp)){//推算上边界
                top_temp--;
            }
            if(this.hasRowCollposed_bottom($tr_bottom,left_temp,right_temp)){//推算下边界
                bottom_temp++;
            }

            if(left_temp == left && right_temp == right && top_temp == top && bottom_temp == bottom){
                break;
            }
            else{
          //有一个边界值改变则重新推算 left
= left_temp; right = right_temp; top = top_temp; bottom = bottom_temp; $tr_top = $trs.eq(top); $tr_bottom = $trs.eq(bottom-1); } } return {left,right,top,bottom}; }

  得到四个边界值就可以得到其内的单元格,以及rowspan属性值大小和colspan属性值大小,最后按照开头说的,选取最终划定矩型左上角的单元格进行合并即可,同行的删除,不同行的隐藏。

  测试的时候重现上述所说的状态,保证所写代码逻辑都走一遍。

 

 

以上是关于前端合并单元格算法-遁地龙卷风的主要内容,如果未能解决你的问题,请参考以下文章

突破瓶颈-遁地龙卷风

圆弧和扇形的加载动画-遁地龙卷风

嵌套调用less函数时参数值的变化及提取部分-遁地龙卷风

许愿池1-遁地龙卷风

Angularjs走了,但生活还是要继续-遁地龙卷风

less封装样式有规律的类选择器-遁地龙卷风