0

0

如何实现可拖动table表头

小云云

小云云

发布时间:2018-02-10 15:30:13

|

2633人浏览过

|

来源于php中文网

原创

首先要说明的是,我们的项目使用的表格大概只分为两类,一类是表头不固定,就是普通的表格,另一类是表头固定,tbody部分是可以滚动的。需要说明的是,表头固定的那种是需要用两个table去实现,做过的人应该也都明白。前者看起来比较简单,因为宽度是受thead里的th影响的,后者看起来就不好处理,因为你用两个table就会出现下面的情况:
如何实现可拖动table表头
emmm,这和我们想象的应该不一样,这可咋整,感觉处理起来很麻烦啊。想起看过element-ui中的表格,似乎有拖动表头的实现,先打开控制台看下结构吧:
如何实现可拖动table表头
呃,话说长这么大我都没用过这两个标签,但仔细观察上面有个width,看到这大概也知道是怎么回事了,打开mdn看下相关属性的描述,和想的一样,width能控制当前列的宽度。
宽度的控制我们是解决了,还有一个问题,就是拖动后,其他列的宽度改怎么改变,如下:

a b c d

如果我拖动a列,改变的宽度应该怎样分配到b,c,d上,我这里是这样处理的,b、c、d有个属性去表示该列是否已经被拖动过了,如果b、c、d都没拖动过,那么把a改变的宽度平分到b、c、d三列的宽度上,如果b、c、d都改变了话,那么只改变最后一列d的宽度。好了,思路已经有了,我们可以去实现了。
事实证明,如果按照上面的设计就太蠢了,已经改成只改变拖动列后面的列且这些列没有改变过宽度。

实现

首先html结构大概是这样的:

a b
1 2

js方面

  constructor (id, options) {
    this._el = document.querySelector(`#${id}`);
    // 实际使用中需要对dom结构进行判断,这里就不做了
    this._tables = Array.from(this._el.querySelectorAll('table'));
    setTimeout(() => this._resolveDom());

    this.store = {
      dragging: false,                 //是否拖动
      draggingColumn: null,            //拖动的对象
      miniWidth: 30,                   //拖动的最小宽度
      startMouseLeft: undefined,       //鼠标点击时的clientX
      startLeft: undefined,            //th右离table的距离
      startColumnLeft: undefined,      //th左离table的距离
      tableLeft: undefined,            //table离页面左边的距离,
      HColumns: [],
      BColumns: [],
    };
  };

添加dom:

云模块网站管理系统3.1.03
云模块网站管理系统3.1.03

云模块_YunMOK网站管理系统采用PHP+MYSQL为编程语言,搭载自主研发的模块化引擎驱动技术,实现可视化拖拽无技术创建并管理网站!如你所想,无限可能,支持创建任何网站:企业、商城、O2O、门户、论坛、人才等一块儿搞定!永久免费授权,包括商业用途; 默认内置三套免费模板。PC网站+手机网站+适配微信+文章管理+产品管理+SEO优化+组件扩展+NEW Login界面.....目测已经遥遥领先..

下载
const [ THeader ] = this._tables;
let TBody;
const Tr = THeader.tHead.rows[0];
const columns = Array.from(Tr.cells);
const Bcolgroup = document.createElement('colgroup');
const cols = columns.map((item, index) => {
  const col = document.createElement('col');
  item.dataset.index = index;
  col.width = +item.offsetWidth;
  return col;
});
cols.reduce((newDom, item) => {
  newDom.appendChild(item);
  return newDom;
}, Bcolgroup);
const HColgroup = Bcolgroup.cloneNode(true);
THeader.appendChild(HColgroup);

//不管是一个table还是两个,都把header和body提出来
if (this._tables.length === 1) {
  const [ , tbody ] = Array.from(THeader.children);
  tbody.remove();
  TBody = THeader.cloneNode();
  TBody.appendChild(Bcolgroup);
  TBody.appendChild(tbody);
  this._el.appendChild(TBody);
} else {
  [ , TBody ] = this._tables;
  TBody.appendChild(Bcolgroup);
}

//拖动时的占位线
const hold = document.createElement('p');
hold.classList.add('resizable-hold');
this._el.appendChild(hold);

上面这块就是添加节点的,对dom进行处理,为了复用,这里我们不管你是表头固定还是表头不固定,我们都拆分为两个table,这样处理起来也方便的多。
然后就是处理手指移到列右侧cursor的值设为col-resize:

handleMouseMove(evt) {
  //...
  if (!this.store.dragging) {
    const rect = target.getBoundingClientRect();
    const bodyStyle = document.body.style;
    if (rect.width > 12 && rect.right - event.pageX < 8) {
      bodyStyle.cursor = 'col-resize';
      target.style.cursor = 'col-resize';
      this.store.draggingColumn = target;
    } else {
      bodyStyle.cursor = '';
      target.style.cursor = 'pointer';
      this.store.draggingColumn = null;
    }
  }
};

需要注意的是,getBoundingClientRect()获取的rigth是元素右侧距离页面左边缘的距离,不是离页面右边缘的距离。这里就是给theadtr添加mousemove事件,当鼠标指针距离右边缘小于8的时候,改变指针形状,然后改变store里的状态,表示此时点击是可以拖动的了。
然后就是mousedown+mousemove+mouseup来处理拖动了:

const handleMouseDown = (evt) => {
  if (this.store.draggingColumn) {
    this.store.dragging = true;

    let { target } = evt;
    if (!target) return;

    const tableEle = THeader;
    const tableLeft = tableEle.getBoundingClientRect().left;
    const columnRect = target.getBoundingClientRect();
    const minLeft = columnRect.left - tableLeft + 30;
    target.classList.add('noclick');

    this.store.startMouseLeft = evt.clientX;
    this.store.startLeft = columnRect.right - tableLeft;
    this.store.startColumnLeft = columnRect.left - tableLeft;
    this.store.tableLeft = tableLeft;

    document.onselectstart = () => false;
    document.ondragstart = () => false;

    hold.style.display = 'block';
    hold.style.left = this.store.startLeft + 'px';

    const handleOnMouseMove = (event) => {
      const deltaLeft = event.clientX - this.store.startMouseLeft;
      const proxyLeft = this.store.startLeft + deltaLeft;

      hold.style.left = Math.max(minLeft, proxyLeft) + 'px';
    };

    // 宽度是这样分配的,举个

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

2

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

golang 循环遍历
golang 循环遍历

本专题整合了golang循环遍历相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.31

Golang人工智能合集
Golang人工智能合集

本专题整合了Golang人工智能相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

76

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

73

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

67

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

19

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
兄弟连高洛峰div+css视频教程
兄弟连高洛峰div+css视频教程

共21课时 | 4.7万人学习

CSS3 教程
CSS3 教程

共18课时 | 5万人学习

Excel 教程
Excel 教程

共162课时 | 14.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号