元素拖拽Resize功能中魔法数字(-2/-18)的成因探究
元素拖拽与Resize功能中的“魔法数字”疑问解答
问题描述
我编写了实现元素拖拽与调整大小(Resize)的代码,可先尝试拖拽div元素,松开鼠标后将光标移至div右侧,会显示调整大小图标,此时拖动鼠标即可改变元素宽度。
我的疑问集中在这行代码中的-2:
let newWidth = Math.max(box.width + xChange -2,20);
在我的实际项目代码中,需要将此处改为-18才能让Resize功能正常运行。想请教这个“魔法数字”与哪些因素相关?为何必须减去2或18才能确保调整大小功能正常工作?
示例代码
HTML部分
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="draggable"> aa aa aaa bbb ccc aaa dd ddde eeef ffffg gggg hhh hheee ave aev </div>
JavaScript部分
var Obj = {}; Obj.enableDraggables = function (enable, doResize = true) { // if (true) return; // console.log("enableDraggables: " + enable); this.dragSelect = function (e) { // console.log("dragSelect"); Obj.changeCursor = false; Obj.dragSrc = this.hasAttribute("dragHandle") ? this.parentNode : this; Obj.lastX = e.pageX; Obj.lastY = e.pageY; //this.style.cursor = "move"; Obj.dragSrc.style.border = "1px solid black"; setTimeout(function() { if (window.getSelection) { if (window.getSelection().empty) { // Chrome window.getSelection().empty(); } else if (window.getSelection().removeAllRanges) { // Firefox window.getSelection().removeAllRanges(); } } else if (document.selection) { // IE? document.selection.empty(); } }, 12); }; this.endDragging = function(e) { Obj.dragSrc = null; this.style.cursor = "default"; Obj.changeCursor = true; }; this.resizeCursor = function(e) { if (!Obj.changeCursor) return; if (!e.target.oldCursor) e.target.oldCursor = e.target.style.cursor; let rect = e.target.getBoundingClientRect(); // if (Math.abs(e.clientX - rect.right) < 10 && Math.abs(e.clientY - rect.bottom) < 10) { // e.target.style.cursor = "se-resize"; if (Math.abs(e.clientX - rect.right) < 10) { e.target.style.cursor = "e-resize"; document.body.doResize = true; } else { e.target.style.cursor = e.target.oldCursor; document.body.doResize = false; } } this.dragDraggable = function (e) { if (!Obj.dragSrc) return; if (document.body.doResize) { let xChange = e.pageX - Obj.lastX; let box = Obj.dragSrc.getBoundingClientRect(); let newWidth = Math.max(box.width + xChange -2,20); Obj.dragSrc.style.width = "" + newWidth + "px"; Obj.lastX = e.pageX; } else { var isStatic = !Obj.dragSrc.style.position || Obj.dragSrc.style.position == "static"; var isFixed = $(Obj.dragSrc).css("position") == "fixed"; if (isFixed) { $(Obj.dragSrc).css("left", parseInt($(Obj.dragSrc).css("left").substring(-2)) + (e.pageX - Obj.lastX) + "px"); $(Obj.dragSrc).css("top", parseInt($(Obj.dragSrc).css("top").substring(-2)) + (e.pageY - Obj.lastY) + "px"); Obj.dragSrc.style.top = e.clientY + "px"; Obj.lastX = e.pageX; Obj.lastY = e.pageY; e.preventDefault(); e.stopPropagation(); return; } if (isStatic) { Obj.dragSrc.style.position = "relative"; Obj.dragSrc.style.left = "0px"; Obj.dragSrc.style.top = "0px"; } if (Obj.dragSrc) { if (Obj.dragSrc.style.left) { var px = parseInt(Obj.dragSrc.style.left.substring(-2)); var moveTo = (px + (e.pageX - Obj.lastX)); Obj.lastX = e.pageX; Obj.dragSrc.style.left = moveTo + "px"; } else if (Obj.dragSrc.style.right) { var px = parseInt(Obj.dragSrc.style.right.substring(-2)); var moveTo = (px + (Obj.lastX - e.pageX)); Obj.lastX = e.pageX; Obj.dragSrc.style.right = moveTo + "px"; } if (Obj.dragSrc.style.top) { var py = parseInt(Obj.dragSrc.style.top.substring(-2)); var moveTo = (py + (e.pageY - Obj.lastY)); Obj.lastY = e.pageY; Obj.dragSrc.style.top = moveTo + "px"; } else if (Obj.dragSrc.style.bottom) { var py = parseInt(Obj.dragSrc.style.bottom.substring(-2)); var moveTo = (py + (Obj.lastY - e.pageY)); Obj.lastY = e.pageY; Obj.dragSrc.style.bottom = moveTo + "px"; } } } }; if(enable) { if (doResize) { $(".draggable").on("mousedown", this.dragSelect).on("mousemove", this.resizeCursor).css("cursor", "grab"); } $("body").on("mouseup", this.endDragging); $("body").on("mousemove", this.dragDraggable); } else { $(".draggable").off("mousedown", this.dragSelect); $("body").off("mouseup", this.endDragging); $("body").off("mousemove", this.dragDraggable); } } Obj.enableDraggables(true, true);
解答
这个“魔法数字”本质是用来抵消元素盒模型额外占用的宽度,核心关联因素如下:
- 元素边框(border):示例代码中给拖拽元素添加了
1px solid black的边框,getBoundingClientRect()返回的width是包含左右边框的总宽度,但默认box-sizing: content-box下,style.width设置的是内容区域宽度,所以需要减去左右边框的总宽度2px,这就是示例里-2的由来。 - 元素内边距(padding):如果元素设置了左右padding,
getBoundingClientRect()的宽度也会包含这部分,此时要减去的数值需要加上左右padding的总和。 - box-sizing属性:若元素设置
box-sizing: border-box,style.width会包含边框和内边距,getBoundingClientRect().width与style.width数值对应,无需额外减去数值,魔法数字会变为0。 - 全局/父元素样式影响:你的项目中需要减18,大概率是元素本身有左右边框+内边距的组合,或是继承了全局样式(如默认padding、border),导致实际额外占用的宽度总和为18px。
必须减去该数值的原因:getBoundingClientRect().width是元素在页面中实际占据的总宽度(包含border、padding),而默认情况下style.width仅控制内容区域宽度。若不抵消这部分额外宽度,调整时会出现元素宽度“跳变”、光标与元素边缘错位的问题——比如拖动鼠标想让元素变宽10px,实际会因为包含边框多宽2px,导致视觉上光标和元素边缘不匹配。
优化方案(避免魔法数字)
推荐两种方式彻底解决这个问题:
- 动态计算额外宽度:
// 获取元素的边框、内边距数值 const computedStyle = window.getComputedStyle(Obj.dragSrc); const borderLeft = parseInt(computedStyle.borderLeftWidth) || 0; const borderRight = parseInt(computedStyle.borderRightWidth) || 0; const paddingLeft = parseInt(computedStyle.paddingLeft) || 0; const paddingRight = parseInt(computedStyle.paddingRight) || 0; const extraWidth = borderLeft + borderRight + paddingLeft + paddingRight; // 计算新宽度 let newWidth = Math.max(box.width + xChange - extraWidth,20);
- 设置box-sizing: border-box:
给拖拽元素添加CSS样式:
.draggable { box-sizing: border-box; }
此时style.width会包含边框和内边距,getBoundingClientRect().width与style.width数值一致,无需再减去额外数值。
内容的提问来源于stack exchange,提问作者ControlAltDel
相关产品推荐
相关产品推荐

