html2canvas截图overflow:hidden失效的问题

本文探讨了图片编辑过程中遇到的图片溢出问题,并提供了使用html2canvas进行截图时确保溢出部分不被包含在内的解决方案。通过调整容器设置及采用分步截图的方法,实现了精确的图片生成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先这个问题会出现在需要图片编辑的业务中,在我们做图片旋转、放大、移动等操作时,被处理的图片会超出原本的容器,此时我们会给父容器增加一个属性overflow:hidden,这样就显示不会出现溢出问题,此时我们去做截图操作生成的图片正常的,hidden部分不会出现,可以有网上提问利用html2canvas保存图片时div中overflow:hidden也显示了出来,像这种情况本人在实际的运用中也是遇到过的。原因其实很简单的。
在html2canvas社区也提出这个问题Not hiding overflow:hidden data

  1. 其实就我们嵌套了两层容器,我们查看通常情况下overflow:hidden不起作用的一种情况,这些都是显示出了问题罢了,先看以代码
    <1>、情景一
//父容器
  <div class="albumpreviewdiv">
          //添加图片的容器
          <div style="overflow: hidden;width: 100%" id="frame_content_add">
            <img  src="static/img/icon_tianjia@3x.png" class="frame_add" id="frame_add" >
          </div>
          //蒙版图 在最上层
          <img class="frame_mask" v-bind:src="getimg(frame_data.img)">
      </div>
  ......
 //截图操作
  var mainCancas = document.createElement("canvas"),
              ctx = mainCancas.getContext('2d');
      mainCancas.width = 800;
      mainCancas.height = 800 / _this.aspect_ratio;
      ctx.rect(0, 0, 800, 800 / _this.aspect_ratio);
      ctx.fillStyle = '#fff';
      ctx.fill();
      html2canvas($('.albumpreviewdiv'), {
        onrendered: function (canvas) {
          ctx.drawImage(canvas,0,0, 800, 800 / _this.aspect_ratio);
          var src = mainCancas.toDataURL();
          }});

效果图:
这里写图片描述 结果还好,没有溢出,这是手机web生成的清晰还好,我的上一篇提到了截图模糊的问题。

<1>、情景二

  <div class="albumitemcontent">
    <a style="overflow: hidden;position: absolute;background:#F2F2F2;display: none" >
        <img  src="static/img/icon_tianjia@3x.png" style="width: 0.5rem;height: 0.5rem;position: absolute" >
        <img style="position: absolute;z-index: 100" onclick="document.querySelector('input').click()" @click="inputBtn($event)" >
    </a>
    <a style="overflow: hidden !important;position: absolute;background:#F2F2F2;display: none" >
        <img  src="static/img/icon_tianjia@3x.png" style="width: 0.5rem;height: 0.5rem;position: absolute" >
        <img style="position: absolute;z-index: 100" onclick="document.querySelector('input').click()" @click="inputBtn($event)">
    </a>
  </div>

此时布局页面变得复杂了,子容器不再是一个了,直接截图的结果就是
效果一我在右边的容器添加的图片截图结果左边也显示出来了,这就很尴尬了。方法那就只能分单个容器来操作了,最后在绘制到同一张画布上去,看代码

  var mainCancas = document.createElement("canvas"),
           ctx = mainCancas.getContext('2d');
   mainCancas.width = 1500/_this.zoom;
   mainCancas.height = 1500/_this.zoom/1.4150;
   ctx.rect(0, 0, 1500/_this.zoom, 1500/_this.zoom/1.4150);
   ctx.fillStyle = '#fff';
   ctx.fill();
   //左边
   html2canvas($('#base_f_'+0), {
                  onrendered: function(canvas0) {
                      ctx.drawImage(canvas0,_this.data[0][0].x/_this.zoom,_this.data[0][0].y/_this.zoom);
    //右边                  
   html2canvas($('#base_s_'+0), {
      onrendered: function(canvas1) {   
      //这里有事需要 设置绘制的初始点,x y值                         ctx.drawImage(canvas1,_this.data[0][1].x/_this.zoom,_this.data[0][1].y/_this.zoom);
                             // window.open(mainCancas.toDataURL("image/png"));                         
                              var url =mainCancas.toDataURL("image/png");
                              });
                          }});

                  }});

这样生成图片就OK了
这里写图片描述

<template> <div id="app"> <div id="container"> <div id="header"> <div class="logo"> <div> <div class="header-title">流程图设计器</div> </div> </div> <div class="button-group"> <button id="save-button" class="button button-save" @click="saveDiagram"><i>💾</i> 保存</button> <button id="reset-button" class="button button-reset" @click="resetDiagram"><i>🔄</i> 重置</button> </div> </div> <div id="canvas-container"> <button id="toggle-sidebar" class="toggle-sidebar" @click="toggleSidebar"> {{ sidebarOpen ? '关闭面板' : '属性面板' }} </button> <div id="canvas"></div> </div> <div id="status-bar"> <div class="status-item"> <div class="status-indicator" :style="statusIndicatorStyle"></div> <span>{{ statusText }}</span> </div> <div class="status-item"> <span>{{ elementCount }} 个元素</span> </div> <div class="status-item"> <span>{{ lastSaveText }}</span> </div> </div> </div> <div id="notification" class="notification" :class="{ show: showNotification, [notificationType]: true }"> {{ notificationMessage }} </div> <div id="sidebar" class="sidebar" :class="{ open: sidebarOpen }"> <h3>元素属性</h3> <div class="properties-group"> <div class="property-item"> <label for="element-name">名称</label> <input type="text" id="element-name" placeholder="输入元素名称" v-model="elementName" @input="updateElementProperties" /> </div> <div class="property-item"> <label for="element-desc">描述</label> <textarea id="element-desc" rows="3" placeholder="输入元素描述" v-model="elementDesc" @input="updateElementProperties" ></textarea> </div> </div> </div> </div> </template> <script> import BpmnJS from 'bpmn-js/dist/bpmn-modeler.development.js' import 'bpmn-js/dist/assets/diagram-js.css' import 'bpmn-js/dist/assets/bpmn-js.css' import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css' export default { name: 'ActivitiDesigner', data() { return { bpmnModeler: null, isModelerReady: false, sidebarOpen: false, showNotification: false, notificationMessage: '', notificationType: '', statusText: '就绪', elementCount: 0, lastSaveText: '未保存', currentElement: null, elementName: '', elementDesc: '', elementBusinessObject: null, } }, computed: { statusIndicatorStyle() { switch (this.statusText) { case '就绪': return { backgroundColor: '#2ecc71' } case '加载中': return { backgroundColor: '#f39c12' } case '保存中': return { backgroundColor: '#3498db' } case '错误': return { backgroundColor: '#e74c3c' } default: return { backgroundColor: '#95a5a6' } } }, }, mounted() { this.initializeModeler() }, methods: { async initializeModeler() { try { this.showNotification = true this.notificationMessage = '正在初始化流程图设计器...' this.notificationType = 'warning' // 创建BPMN模型实例 this.bpmnModeler = new BpmnJS({ container: '#canvas', keyboard: { bindTo: document, }, }) // 监听模型就绪事件 this.bpmnModeler.on('import.done', (event) => { console.log('BPMN模型加载完成') this.isModelerReady = true this.statusText = '就绪' // 更新元素计数 this.updateElementCount() // 显示成功通知 this.showNotificationMessage('流程图加载完成!', 'success') // 监听元素选择事件 this.bpmnModeler.on('element.click', (event) => { this.handleElementSelection(event.element) }) }) // 监听错误事件 this.bpmnModeler.on('error', (err) => { console.error('BPMN模型错误', err) this.isModelerReady = false this.statusText = '错误' this.showNotificationMessage(`模型错误: ${err.message}`, 'error') }) // 监听元素变化事件 this.bpmnModeler.on('element.changed', () => { this.updateElementCount() }) // 创建新流程图 await this.createNewDiagram() } catch (err) { console.error('初始化BPMN模型失败', err) this.showNotificationMessage(`初始化失败: ${err.message}`, 'error') } }, async createNewDiagram() { if (!this.bpmnModeler) return try { this.statusText = '加载中' await this.bpmnModeler.createDiagram() console.log('创建流程图成功') this.lastSaveText = '未保存' this.showNotificationMessage('已创建新流程图', 'success') this.statusText = '就绪' } catch (err) { console.error('创建流程图失败', err) this.showNotificationMessage(`创建流程图失败: ${err.message}`, 'error') this.statusText = '错误' } }, async saveDiagram() { if (!this.bpmnModeler || !this.isModelerReady) { this.showNotificationMessage('模型尚未准备好,请稍后再试', 'warning') return } try { this.statusText = '保存中' // 保存XML const { xml } = await this.bpmnModeler.saveXML({ format: true }) console.log('BPMN XML:', xml) // 在实际应用中,这里可以发送到服务器保存 // await this.saveToServer(xml); // 更新状态 const now = new Date() this.lastSaveText = `最后保存: ${now.toLocaleTimeString()}` // 显示成功通知 this.showNotificationMessage('流程图保存成功!XML内容已输出到控制台', 'success') // 恢复状态 setTimeout(() => { this.statusText = '就绪' }, 1000) } catch (err) { console.error('保存失败', err) this.showNotificationMessage(`保存失败: ${err.message}`, 'error') this.statusText = '错误' } }, resetDiagram() { if (confirm('确定要重置吗?当前内容将丢失。')) { this.createNewDiagram() this.elementName = '' this.elementDesc = '' this.currentElement = null } }, toggleSidebar() { this.sidebarOpen = !this.sidebarOpen }, updateElementCount() { if (!this.bpmnModeler) return try { const elementRegistry = this.bpmnModeler.get('elementRegistry') this.elementCount = elementRegistry ? elementRegistry.getAll().length : 0 } catch (err) { console.error('更新元素计数失败', err) } }, showNotificationMessage(message, type) { this.notificationMessage = message this.notificationType = type this.showNotification = true setTimeout(() => { this.showNotification = false }, 3000) }, handleElementSelection(element) { this.currentElement = element this.elementBusinessObject = element.businessObject console.log(element,'element') // 加载元素属性 this.elementName = this.elementBusinessObject.name || '' this.elementDesc = this.elementBusinessObject.description || '' // 打开属性面板 this.sidebarOpen = true }, updateElementProperties() { if (!this.currentElement || !this.bpmnModeler || !this.elementBusinessObject) return try { const modeling = this.bpmnModeler.get('modeling') // 更新名称 modeling.updateProperties(this.currentElement, { name: this.elementName, description: this.elementDesc, }) this.showNotificationMessage('元素属性已更新', 'success') } catch (err) { console.error('更新元素属性失败', err) this.showNotificationMessage(`更新属性失败: ${err.message}`, 'error') } }, }, } </script> <style scoped> * { box-sizing: border-box; margin: 0; padding: 0; } body, html { height: 100%; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); color: #333; overflow: hidden; } #container { display: flex; flex-direction: column; height: calc(100vh - 150px) ; max-width: 100%; margin: 0 auto; box-shadow: 0 0 30px rgba(0, 0, 0, 0.1); } #header { background: linear-gradient(to right, #2c3e50, #4a6491); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 10; } .logo { display: flex; align-items: center; gap: 15px; } .logo-icon { font-size: 28px; color: #3498db; } .header-title { font-size: 22px; font-weight: 600; } .header-subtitle { font-size: 14px; opacity: 0.8; margin-top: 3px; } .button-group { display: flex; gap: 12px; } .button { padding: 10px 20px; border: none; border-radius: 5px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } .button:active { transform: translateY(0); } .button-save { background: linear-gradient(to right, #27ae60, #2ecc71); color: white; } .button-reset { background: linear-gradient(to right, #e74c3c, #c0392b); color: white; } .button-export { background: linear-gradient(to right, #3498db, #2980b9); color: white; } #canvas-container { flex: 1; position: relative; overflow: hidden; background: #f8f9fa; } #canvas { height: 100%; width: 100%; } #status-bar { background: #2c3e50; color: #ecf0f1; padding: 8px 20px; font-size: 14px; display: flex; justify-content: space-between; align-items: center; } .status-item { display: flex; align-items: center; gap: 8px; } .status-indicator { width: 10px; height: 10px; border-radius: 50%; background-color: #2ecc71; } .notification { position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 5px; color: white; font-weight: 500; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); z-index: 1000; opacity: 0; transform: translateX(100%); transition: all 0.4s ease; } .notification.show { opacity: 1; transform: translateX(0); } .notification.success { background: linear-gradient(to right, #27ae60, #2ecc71); } .notification.error { background: linear-gradient(to right, #e74c3c, #c0392b); } .notification.warning { background: linear-gradient(to right, #f39c12, #e67e22); } .sidebar { position: absolute; top: 260px; right: 20px; width: 300px; height: 50%; background: white; box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1); z-index: 5; padding: 20px; overflow-y: auto; display: none; } .sidebar.open { display: inline } .sidebar h3 { margin-bottom: 15px; color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; } .properties-group { margin-bottom: 20px; } .property-item { margin-bottom: 15px; } .property-item label { display: block; margin-bottom: 5px; font-weight: 500; color: #2c3e50; } .property-item input, .property-item textarea, .property-item select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .toggle-sidebar { position: absolute; top: 20px; right: 20px; background: #3498db; color: white; border: none; border-radius: 5px; padding: 8px 15px; cursor: pointer; z-index: 6; } .tools-palette { position: absolute; top: 20px; left: 20px; background: white; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); padding: 15px; z-index: 5; display: flex; flex-direction: column; gap: 10px; } .tool-btn { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #f8f9fa; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s; } .tool-btn:hover { background: #3498db; color: white; transform: scale(1.1); } .tool-btn.active { background: #3498db; color: white; } </style> 描述没有更新
最新发布
07-19
<!DOCTYPE html> <html> <head> <title>奥特曼战斗动画</title> <style> body { margin: 0; overflow: hidden; background: #000; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: Arial, sans-serif; } canvas { border: 1px solid #333; box-shadow: 0 0 20px rgba(255,255,255,0.2); } .controls { position: fixed; bottom: 20px; color: white; text-align: center; } button { background: linear-gradient(45deg, #ff5e00, #ffb700); border: none; padding: 10px 20px; color: white; border-radius: 25px; cursor: pointer; font-weight: bold; margin: 5px; transition: 0.3s; } button:hover { transform: scale(1.05); } </style> </head> <body> <canvas id="battleCanvas"></canvas> <div class="controls"> <button onclick="startBattle()">开始战斗</button> <button onclick="resetScene()">重置场景</button> </div> <script> const canvas = document.getElementById('battleCanvas'); const ctx = canvas.getContext('2d'); canvas.width = 800; canvas.height = 600; // 角色定义 const ultraman = { x: 200, y: 400, width: 150, height: 250, color: '#0077ff', energy: 100, draw() { // 绘制奥特曼 ctx.fillStyle = this.color; ctx.beginPath(); ctx.moveTo(this.x, this.y); ctx.lineTo(this.x + 50, this.y - 100); ctx.lineTo(this.x + 100, this.y - 150); ctx.lineTo(this.x + 150, this.y - 100); ctx.lineTo(this.x + 200, this.y); ctx.lineTo(this.x + 150, this.y + 150); ctx.lineTo(this.x + 100, this.y + 200); ctx.lineTo(this.x + 50, this.y + 150); ctx.closePath(); ctx.fill(); // 能量指示器 ctx.fillStyle = '#00ff00'; ctx.fillRect(this.x, this.y + 220, this.energy * 1.5, 10); } }; const hero = { x: 600, y: 400, width: 100, height: 200, color: '#111', energy: 100, draw() { // 绘制角色(根据用户描述) ctx.fillStyle = this.color; ctx.fillRect(this.x, this.y - 180, 80, 180); // 制服细节 ctx.fillStyle = '#fff'; ctx.fillRect(this.x + 10, this.y - 150, 60, 5); ctx.fillStyle = '#ff0'; ctx.fillRect(this.x + 10, this.y - 140, 60, 3); // 耳机 ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(this.x + 40, this.y - 190, 10, 0, Math.PI*2); ctx.fill(); // 能量指示器 ctx.fillStyle = '#ff0000'; ctx.fillRect(this.x, this.y + 20, this.energy * 0.8, 10); } }; // 特效系统 const effects = []; function addEffect(x, y, color, size) { effects.push({ x, y, color, size, life: 20, draw() { ctx.globalAlpha = this.life/20; ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size * (1 - this.life/20), 0, Math.PI*2); ctx.fill(); ctx.globalAlpha = 1; this.life--; } }); } // 战斗动画 let animating = false; let frame = 0; function battleLoop() { if(!animating) return; ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制背景 ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制地面 ctx.fillStyle = '#333'; ctx.fillRect(0, 500, canvas.width, 100); // 更新战斗状态 if(frame % 30 === 0) { ultraman.energy -= 5; hero.energy -= 3; } // 攻击特效 if(frame % 15 === 0) { addEffect( ultraman.x + 100, ultraman.y - 50, '#00ffff', 30 ); } // 绘制角色 ultraman.draw(); hero.draw(); // 绘制特效 effects.forEach(e => e.draw()); effects = effects.filter(e => e.life > 0); // 绘制UI ctx.fillStyle = '#fff'; ctx.font = '20px Arial'; ctx.fillText(`奥特曼能量: ${ultraman.energy}%`, 20, 30); ctx.fillText(`战士能量: ${hero.energy}%`, 600, 30); // 胜负判定 if(ultraman.energy <= 0 || hero.energy <= 0) { animating = false; ctx.fillStyle = ultraman.energy <= 0 ? '#ff0000' : '#00ff00'; ctx.fillText(ultraman.energy <= 0 ? "战士胜利!" : "奥特曼胜利!", 350, 100); } frame++; requestAnimationFrame(battleLoop); } function startBattle() { if(animating) return; ultraman.energy = 100; hero.energy = 100; effects.length = 0; animating = true; frame = 0; battleLoop(); } function resetScene() { animating = false; ctx.clearRect(0, 0, canvas.width, canvas.height); ultraman.draw(); hero.draw(); } // 初始绘制 resetScene(); </script> </body> </html> 上面的代码帮我生成网址
07-07
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值