首页 > 其他分享 >js实现拖动调节宽高的iframe

js实现拖动调节宽高的iframe

时间:2025-01-10 11:55:48浏览次数:1  
标签:style const 宽高 js iframeContainer resize iframe Math

需求

我有一个用vue3项目实现的ai聊天功能。使用js文件的形式来引入其它项目,具体的表现显示是一个机器人icon,点开就是iframe。但是定死iframe的宽高,就显得不够灵活。所以我打算做一下类似windows窗体那样的拖拽调整宽高。具体代码也借助了chat AI,如果完全自己实现还是很耗时间的。

另:实际上这个项目还有多分辨率适配、拖拽移动之类的优化。不过这篇文章只简单说明一下iframe调整宽高。

效果

iframe拖拽调整宽高.gif

dom结构

拖拽iframe的dom结构.png

实现思路

拖拽调节宽高实际上是一个还算常见的需求,只不过对于iframe来说需要有一些额外的处理,例如手动添加的在iframe之上的拖拽div,以及拖拽时的遮罩层。
(实际上我第一个想到的css的resize属性,但是效果不好)

1.创建一个包裹iframe的iframe-container,其中包括:iframe、调整宽高用的div、遮罩层div。

2.为每个调整宽高的div添加 mousedown 事件监听器,记录初始位置和尺寸,并且启动遮罩层。

  • 在 mousemove 事件中,根据鼠标移动的距离计算新的尺寸和位置,并更新 iframe 容器的样式。
  • 在 mouseup 事件中,停止调整大小,并隐藏遮罩层

结构展示

让我把上面那些class为"resize-handle"的div改个颜色, 就明显多了。

resizeHandle涂成黑色.png
如图所示。我希望用户可以通过左边框、上边框和左上角来拖拽调节大小,所以就是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>

上述代码的效果:

简化html效果.gif

最后

我自己的代码写的有点乱(纯js代码),拿copilot生成的简化代码似乎更乱了,dom的代码还分开写在了body和script中。我看着都有点迷惑了(悲。

标签:style,const,宽高,js,iframeContainer,resize,iframe,Math
From: https://www.cnblogs.com/m1pha/p/18663720

相关文章

  • ThreeJS入门(217):THREE.DRACOExporter 知识详解,示例代码
    作者:还是大剑师兰特,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,webgl,ThreeJS,canvas,echarts等技术开发,欢迎加微信(gis-dajianshi),一起交流。查看本专栏目录-本文是第217篇入门文章......
  • JS-26 字符串方法_trim()
    trim方法用于去除字符串两端的空格,返回一个新的字符串,不改变源字符串'zifuchuan'.trim()//"helloworld" 该方法去除的不仅仅是空格,还包括制表符(\t、\v)、换行符(\n)和回车符(\r)'\r\zifuchuan\t'.trim()//'zifuchuan' ES6扩展方法,trimEnd和trimStart()方法" zifuchua......
  • JS-25 字符串方法_indexOF()
    indexOf方法用于确定一个字符串再另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回-1,就表示不匹配'helloworld'.indexOf('o')//4'helloworld'.indexOf('a')//4-1'床前明月光,疑是地上霜。一去二三里,举头望明月,低头思故乡。'.indexOf("一去二三里")//12 i......
  • 基于JAVA中的spring框架和jsp实现门禁管理系统项目【内附项目源码+论文说明】
    摘要门禁管理系统主要用在了企业中,职工可以通过门禁进行通道的访问和通过,管理员可以对企业员工进行门禁的限制,通道可以限定通过的时间和日期,也能对某个特种用户进行限制通行,所以基与互联网的门禁管理系统也就孕育而生了。门禁管理系统就是能够使学生通过互联网完成毕业设计......
  • HTML+CSS+JS制作中国传统节日主题网站(内附源码,含5个页面)
    一、作品介绍HTML+CSS+JS制作一个中国传统节日主题网站,包含首页、节日介绍页、民俗文化页、节日活动页、联系我们页等5个静态页面。其中每个页面都包含一个导航栏、一个主要区域和一个底部区域。二、页面结构1.顶部横幅区包含传统中国风格的网站标题'中国传统节日',配以传......
  • JSP考试系统的设计与实现qih9c(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着信息技术的飞速发展,教育领域正经历着深刻的变革。传统的考试方式已难以满足现代教育的需求,因此,设计并实现一套高效、智能的......
  • JSP看星星—校园文艺作品展示平台ibvcy--(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景与意义随着校园文化的日益丰富,学生们对文艺创作的热情也日益高涨。然而,传统的展示方式往往受限于时间和空间,无法满足广大学生的展示需......
  • JSP咖啡饮品点餐系统7hzll--程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着咖啡文化的普及和消费习惯的变化,咖啡饮品店已经成为人们日常休闲的重要场所。然而,传统的点餐方式存在排队等待、人工错误等......
  • json序列化时,默认遇到中文会转换成unicode,如果想要保留中文怎么办?
    在使用Python的json模块进行序列化时,默认情况下会将中文转换为Unicode编码。如果你希望在序列化时保留中文,可以通过设置ensure_ascii=False来实现。以下是示例代码:importjsondata={"name":"李浩瑞","age":30}#默认行为(中文会被转换成Unicode)json_def......
  • JSP考勤系统设计与实现4jhkn(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、项目背景与意义随着企业规模的扩大和管理的精细化,传统的考勤方式已难以满足现代企业的需求。因此,设计并实现一套高效、智能的考勤系统显得尤为......