需求
我有一个用vue3项目实现的ai聊天功能。使用js文件的形式来引入其它项目,具体的表现显示是一个机器人icon,点开就是iframe。但是定死iframe的宽高,就显得不够灵活。所以我打算做一下类似windows窗体那样的拖拽调整宽高。具体代码也借助了chat AI,如果完全自己实现还是很耗时间的。
另:实际上这个项目还有多分辨率适配、拖拽移动之类的优化。不过这篇文章只简单说明一下iframe调整宽高。
效果
dom结构
实现思路
拖拽调节宽高实际上是一个还算常见的需求,只不过对于iframe来说需要有一些额外的处理,例如手动添加的在iframe之上的拖拽div,以及拖拽时的遮罩层。
(实际上我第一个想到的css的resize属性,但是效果不好)
1.创建一个包裹iframe的iframe-container,其中包括:iframe、调整宽高用的div、遮罩层div。
2.为每个调整宽高的div添加 mousedown
事件监听器,记录初始位置和尺寸,并且启动遮罩层。
- 在
mousemove
事件中,根据鼠标移动的距离计算新的尺寸和位置,并更新 iframe 容器的样式。 - 在
mouseup
事件中,停止调整大小,并隐藏遮罩层
结构展示
让我把上面那些class为"resize-handle"的div改个颜色, 就明显多了。
如图所示。我希望用户可以通过左边框、上边框和左上角来拖拽调节大小,所以就是class为"t"、"l"、"lt"的div在图片中涂成了黑色(注意class为"rb"的div实际上被我display: none了,这原本是右下角的div)。
注:t = top, l = left。
让copilot帮我生成的简单代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resizable Iframe</title>
<style>
.resizable-iframe-container {
position: relative;
display: inline-block;
}
.resizable-iframe {
width: 100%;
height: 100%;
border: none;
}
.resize-handle {
position: absolute;
background-color: #000;
z-index: 99999;
}
.resize-handle.rb {
width: 4px;
height: 4px;
right: -8px;
bottom: -8px;
cursor: se-resize;
}
.resize-handle.lt {
width: 6px;
height: 6px;
left: 1px;
top: 1px;
cursor: nw-resize;
z-index: 100000;
}
.resize-handle.l {
width: 6px;
height: 100%;
left: 1px;
top: 0;
cursor: w-resize;
}
.resize-handle.t {
width: 100%;
height: 6px;
top: 1px;
left: 0;
cursor: n-resize;
}
</style>
</head>
<body>
<div id="iframeContainer" class="resizable-iframe-container">
<iframe id="resizableIframe" class="resizable-iframe" src="https://www.example.com"></iframe>
</div>
<script>
function createIframeOverlay() {
const iframeOverlay = document.createElement('div');
Object.assign(iframeOverlay.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
zIndex: '99999',
display: 'none',
});
return iframeOverlay;
}
function enableResize(iframeContainer, iframe, iframeOverlay) {
let isResizing = false;
let startX, startY, startWidth, startHeight, startLeft, startTop, resizeType;
const resizeHandles = iframeContainer.querySelectorAll('.resize-handle');
resizeHandles.forEach((handle) => {
handle.addEventListener('mousedown', (e) => {
isResizing = true;
resizeType = handle.classList[1];
startX = e.clientX;
startY = e.clientY;
startWidth = iframeContainer.offsetWidth;
startHeight = iframeContainer.offsetHeight;
startLeft = iframeContainer.offsetLeft;
startTop = iframeContainer.offsetTop;
iframeOverlay.style.display = 'block';
document.addEventListener('mousemove', onBorderMouseMove);
document.addEventListener('mouseup', onBorderMouseUp);
});
});
function onBorderMouseMove(e) {
if (isResizing) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const minWidth = 10;
const minHeight = 10;
const maxWidth = 1000;
const maxHeight = 1000;
let newWidth, newHeight, newLeft, newTop;
switch (resizeType) {
case 'rb':
newWidth = Math.min(Math.max(startWidth + dx, minWidth), maxWidth);
newHeight = Math.min(Math.max(startHeight + dy, minHeight), maxHeight);
iframeContainer.style.width = `${newWidth}px`;
iframeContainer.style.height = `${newHeight}px`;
break;
case 'lt':
newWidth = Math.min(Math.max(startWidth - dx, minWidth), maxWidth);
newHeight = Math.min(Math.max(startHeight - dy, minHeight), maxHeight);
if (newWidth > minWidth && newWidth < maxWidth) {
newLeft = startLeft + dx;
iframeContainer.style.left = `${newLeft}px`;
}
if (newHeight > minHeight && newHeight < maxHeight) {
newTop = startTop + dy;
iframeContainer.style.top = `${newTop}px`;
}
iframeContainer.style.width = `${newWidth}px`;
iframeContainer.style.height = `${newHeight}px`;
break;
case 'l':
newWidth = Math.min(Math.max(startWidth - dx, minWidth), maxWidth);
if (newWidth > minWidth && newWidth < maxWidth) {
newLeft = startLeft + dx;
iframeContainer.style.left = `${newLeft}px`;
}
iframeContainer.style.width = `${newWidth}px`;
break;
case 't':
newHeight = Math.min(Math.max(startHeight - dy, minHeight), maxHeight);
if (newHeight > minHeight && newHeight < maxHeight) {
newTop = startTop + dy;
iframeContainer.style.top = `${newTop}px`;
}
iframeContainer.style.height = `${newHeight}px`;
break;
}
iframe.style.width = iframeContainer.style.width;
iframe.style.height = iframeContainer.style.height;
}
}
function onBorderMouseUp() {
isResizing = false;
iframeOverlay.style.display = 'none';
document.removeEventListener('mousemove', onBorderMouseMove);
document.removeEventListener('mouseup', onBorderMouseUp);
}
}
function onResize(iframeEle, callback) {
const observer = new ResizeObserver(callback);
observer.observe(iframeEle);
}
const iframeContainer = document.getElementById('iframeContainer');
const iframe = document.getElementById('resizableIframe');
// 添加缩放控制点
iframeContainer.innerHTML += `
<div class="resize-handle rb"></div>
<div class="resize-handle lt"></div>
<div class="resize-handle l"></div>
<div class="resize-handle t"></div>
`;
// 在 iframeContainer 中添加一个遮罩层元素
const iframeOverlay = createIframeOverlay();
iframeContainer.appendChild(iframeOverlay);
iframe.classList.add('resizable-iframe');
enableResize(iframeContainer, iframe, iframeOverlay);
// 保留回调函数入口
onResize(iframe, () => {
console.log('Iframe resized');
});
</script>
</body>
</html>
上述代码的效果:
最后
我自己的代码写的有点乱(纯js代码),拿copilot生成的简化代码似乎更乱了,dom的代码还分开写在了body和script中。我看着都有点迷惑了(悲。
标签:style,const,宽高,js,iframeContainer,resize,iframe,Math From: https://www.cnblogs.com/m1pha/p/18663720