CSS拖拽样式不用JavaScript?新出的:drag伪类到底啥情况

2,139字
9–14 分钟
in

摘要:拖拽元素时想改变样式,以前得靠JavaScript监听dragstart和dragend事件来回切换类名。现在CSS工作组打算整一个新活,直接搞个:drag伪类,让拖拽中的样式纯CSS就能搞定。更离谱的是还琢磨了个::drag-image伪元素,连拖拽时那张跟随鼠标的预览图都能自定义样式,再也不用折腾setDragImage那一大坨代码了。这篇文章就掰扯掰扯这俩新家伙怎么用,跟传统JS方案比到底香在哪。

目录

这玩意儿是啥

:drag是CSS正在讨论的一个新伪类,专门用来命中正在被拖拽的元素。啥叫拖拽?就是用鼠标点着元素不松手,然后满屏幕划拉的那个过程。以前CSS压根没法感知这个状态,想做拖拽中的视觉反馈,比如半透明、加阴影、放大缩小啥的,都得靠JS在dragstart时给元素塞个类名,dragend时再给它摘掉。

硬核拖拽咋整

先拿JS这套流程走一遍,这样才能明白新伪类省了多少事。

<div class="task-list" draggable="true">
  <div class="task-item">写周报</div>
  <div class="task-item">开会</div>
  <div class="task-item">回邮件</div>
</div>

要给这整个任务面板加拖拽样式,JS得监听俩事件:

let dragPanel = document.querySelector(".task-list");

dragPanel.addEventListener("dragstart", (ev) => {
  ev.target.classList.add("dragging-active");
});

dragPanel.addEventListener("dragend", (ev) => {
  ev.target.classList.remove("dragging-active");
});

CSS那边就等着这个类名出现:

.dragging-active {
  opacity: 0.6;
  box-shadow: 0 8px 20px rgba(0,0,0,0.2);
  transform: scale(0.98);
  cursor: grabbing;
}

这么写没啥毛病,就是每次想做拖拽样式都得重复这套监听逻辑,项目里拖拽元素一多,到处都在addEventListener,看着都头大。

纯CSS横着走

有了:drag伪类之后,上面那堆JS全都可以扔了。HTML里只要给元素加上draggable=”true”,CSS直接写:

.task-list:drag {
  opacity: 0.6;
  box-shadow: 0 8px 20px rgba(0,0,0,0.2);
  transform: scale(0.98);
  cursor: grabbing;
}

这玩意儿在浏览器里跑起来,只要鼠标点着元素开始拖,样式立马生效,松手就恢复,完全不需要JS介入。代码量直接从十几行压缩到五行,维护起来也省心,想改拖拽样式直接去CSS里找:drag就完事了。

偷梁换柱

拖拽的时候浏览器默认会生成一个元素的半透明“幽灵图”跟着鼠标跑,这图叫预览图像。以前想换掉它,得用dataTransfer.setDragImage方法手动造一个div塞进去:

let customPreview = document.createElement("div");
customPreview.textContent = "📁 正在移动...";
customPreview.style.cssText = `
  background: #f0f0f0;
  border: 2px solid #333;
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
`;
document.body.appendChild(customPreview);
event.dataTransfer.setDragImage(customPreview, 10, 10);
setTimeout(() => customPreview.remove(), 0);

这代码看着就啰嗦,还得自己手动管理这个div的创建和销毁,稍微不注意就会在页面上留下垃圾节点。

换个思路

社区里有人提议搞个::drag-image伪元素,直接在CSS里定义拖拽预览图长啥样:

::drag-image {
  content: "📁 正在移动...";
  background: #f0f0f0;
  border: 2px solid #333;
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

这里有个细节,::drag-image是伪元素,不是伪类。伪类代表状态,比如:drag表示“正在拖拽中”这个状态;伪元素代表一个具体的东西,预览图像就是拖拽过程中实际存在的一个独立元素。按这个逻辑,自定义预览图用伪元素更贴切,它就是个可以被样式化的对象。

真要能用上这个,拖拽预览图的定制就变成了纯CSS的活,不用再跟JS里的setDragImage较劲,性能更好,代码也更干净。