原生js实现拖动改变弹窗大小,同时修改显示内容显示比例
代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#testDialog {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 300px;
padding: 2px;
font-size: 20px;
background-color: indianred;
}
#content-wrapper {
width: 100%;
height: 100%;
font-size: 1em;
word-break: break-all;
overflow-y: auto;
}
</style>
</head>
<body>
<div id="testDialog">
<div id="content-wrapper">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
</div>
</div>
</body>
<script>
const testDialog = document.getElementById('testDialog') // 获取弹窗dom
let initialSize = [testDialog.clientWidth, testDialog.clientHeight] // 记录弹窗初始宽高
let dialogChangeStatus = 'none' // 设置弹窗状态,none为鼠标未按下状态,l在左侧拖动、r在右侧拖动、t在上侧、b下侧
let mouseDownCoordinate = [NaN, NaN] // 记录点击时鼠标对应屏幕坐标
window.addEventListener('mousedown', setCoordinate)
window.addEventListener('mouseup', clearCoordinate)
window.addEventListener('mousemove', function(e) {
if (!isNaN(mouseDownCoordinate[0])) { // 根据是否存在鼠标按下时位置作为事件触发条件
let newWidth = 0
let newHeight = 0
if (dialogChangeStatus === 'r') { // 弹窗右侧边缘
const moveX = e.screenX - mouseDownCoordinate[0]
newWidth = initialSize[0] + moveX
testDialog.style.width = newWidth + 'px'
} else if (dialogChangeStatus === 'l') { // 弹窗左侧边缘
const moveX = mouseDownCoordinate[0] - e.screenX
newWidth = initialSize[0] + moveX
testDialog.style.width = newWidth + 'px'
} else if (dialogChangeStatus === 't') { // 弹窗上侧边缘
const moveY = mouseDownCoordinate[1] - e.screenY
newHeight = initialSize[1] + moveY
testDialog.style.height = newHeight + 'px'
} else if (dialogChangeStatus === 'b') { // 弹窗下侧边缘
const moveY = e.screenY - mouseDownCoordinate[1]
newHeight = initialSize[1] + moveY
testDialog.style.height = newHeight + 'px'
}
// 仅针对宽度变化进行展示内容的调整
if (dialogChangeStatus === 'r' || dialogChangeStatus === 'l') { // 设置父级font-size
testDialog.style.fontSize = newWidth / 20 +'px'
}
}
})
function setCoordinate(e) {
// 由于transform: translate(-50%, -50%)原因需要减去一半宽高
let restX = e.x - (testDialog.offsetLeft - testDialog.clientWidth / 2)
let restY = e.y - (testDialog.offsetTop - testDialog.clientHeight / 2)
let flag = ''
if (restX <= 2 && restX >= 0) {
flag = 'l'
}
if (restX < testDialog.clientWidth && restX >= testDialog.clientWidth - 2) {
flag = 'r'
}
if (restY <= 2 && restX >= 0) {
flag = 't'
}
if (restY < testDialog.clientHeight && restY >= testDialog.clientHeight - 2) {
flag = 'b'
}
setDialogChangeStatus(flag)
mouseDownCoordinate = [e.screenX, e.screenY]
}
// 鼠标按键抬起时重置相关参数
function clearCoordinate(e) {
mouseDownCoordinate = [NaN, NaN]
initialSize = [testDialog.clientWidth, testDialog.clientHeight]
testDialog.style.cursor = 'auto'
testDialog.style.userSelect = 'unset'
dialogChangeStatus = 'none'
}
testDialog.addEventListener('mousemove', function(e) { // 监听弹框鼠标移入事件
// 当鼠标位于左右两边2px时可进行拉伸操作,切换鼠标图标展示效果
if (e.offsetX <= 2 || e.offsetX >= e.target.clientWidth - 2) {
e.target.style.cursor = 'w-resize'
e.target.style.userSelect = 'none' // 到达可拖动位置时去除文本选中效果
} else if (e.offsetY <= 2 || e.offsetY >= e.target.clientHeight - 2) {
e.target.style.cursor = 's-resize'
e.target.style.userSelect = 'none'
} else {
e.target.style.cursor = 'unset'
e.target.style.userSelect = 'unset'
}
})
// 本意通过强制设定样式,保持图标显示效果但没能实现,即switch部分并无实际作用
function setDialogChangeStatus(flag) {
dialogChangeStatus = flag
switch (flag) {
case 't':
testDialog.style.cursor = 's-resize'
testDialog.style.userSelect = 'none'
break
case 'b':
testDialog.style.cursor = 's-resize'
testDialog.style.userSelect = 'none'
break
case 'l':
testDialog.style.cursor = 'w-resize'
testDialog.style.userSelect = 'none'
break
case 'r':
testDialog.style.cursor = 'w-resize'
testDialog.style.userSelect = 'none'
break
default:
testDialog.style.cursor = 'unset'
testDialog.style.userSelect = 'unset'
break
}
}
</script>
</html>
切换效果
涉及原理
尺寸
广大前端大佬们都所熟知的em尺寸单位,基于父元素的font-size,所以想要根据窗口大小去动态调整显示大小的话只需要通过js代码去动态的设置父元素的font-size即可
不用scale的原因
初始尝试时有想过使用transform的scale属性,但是因为本身放大比例和弹窗放大比例并不同步的问题可能会出现如下情况
大概想了一下,应该还要计算一个scale与外弹窗缩放比例的关系,过于复杂了,不如还是使用基础属性
鼠标拖动及弹窗缩放关系
这边的设计是增加mousedown的事件监听,在鼠标按下时记下此时鼠标点击位置基于屏幕的坐标位置,然后通过mousemove监听事件将mousedown到mouseup这一过程中鼠标的上下左右坐标变化的距离作为弹窗样式的宽高的增加值(可为负)
交互效果及事件触发条件
我个人这边设定的是将弹窗四周大约2像素的宽度作为可拖动事件触发区域,通过修改style的cursor属性展示鼠标图标缩放交互效果,事件的操作是通过全局的监听事件实现,交互效果通过弹窗本身的监听事件触发
注意事项
- 弹窗的左右边缘及上下边缘对应的宽高附加值是颠倒的
- 弹窗的完全居中效果是通过如下方式实现的,所以在进行弹窗位置判定时(offsetLeft、offsetTop)需要将dom元素的一半宽高计算进去
top: 50%; left: 50%; transform: translate(-50%, -50%);
- 若要实现弹窗内全内容缩放效果,那么需将弹窗内所有内容的尺寸单位都设置为em,若不需要变化的部分则可继续使用px作为展示
- div本身不带缩放效果,故而拖动鼠标时会触发文本选中效果,所以需要额外添加user-select属性为none,取消鼠标选中效果
拓展
- 还可以补充四角的拉伸功能,即同时触发纵向拉伸与横向拉伸的效果,只需将原判断条件增加上&&即可
- 部分内容的大小比例(如图片)仍然可以通过scale方式进行设置,通过统一的class进行设置即可