沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 必看导言

    • 就业的核心问题

      • 课件
    • 表现力的训练

      • 课件
  • 简历制作

    • 课件
    • 简历准备课堂笔记
    • 个人信息课堂笔记
    • 求职意向课堂笔记
    • 技术栈课堂笔记
    • 教育经历课堂笔记
    • 个人优势与评价课堂笔记
    • 工作经历课堂笔记
    • 项目
    • 项目亮难点问题

    • 项目经历

  • 项目准备

    • 课件
    • 课件
  • 技术重点

    • 划重点

    • 非技术环节
  • 简历投递和面试准备

    • 简历投递
    • 面试准备
  • 笔面试环节知识讲解
  • 项目准备
  • 难点攻关
  • 网页复制成图片至剪贴板
  • 课件资料
luzhichang
2024-09-18
目录

05-技术讲解

# 网页复制成图片至剪贴板技术讲解

# 什么问题

在网页中,经常能看到一键复制网页内容到剪贴板的功能。要实现这个功能倒并不困难,浏览器有 Clipboard API 来做和剪贴板相关的操作。

但是在一些 Web 应用中,还支持直接将网页复制成图片到剪贴板,例如飞书:

image-20240629202530547

支持将网页某一部分内容复制成图片,然后放入剪贴板,这个就需要我们做一些额外的处理了。

# 解决思路

  1. 第一步是获取该区域的 DOM 元素
const ele = document.getElementById("target");

获取到该区域最外层的 DOM 元素后,需要做一些额外的处理:

  1. 将 DOM 转为 Canvas
  2. 将 Canvas 转为 Blob
  3. 将 Blob 写入到剪贴板

# 解决细节

1. 将 DOM 转为 Canvas

这会涉及到对整棵 DOM 树的解析,以及样式计算等工作。

整个流程整理下来:

  1. 初始化与配置:
    • 接收用户传入的 HTML 元素及配置选项。
    • 设置默认配置选项,并将用户配置与默认配置合并。
  2. 解析和计算样式:
    • 遍历 DOM 树,收集所有需要绘制的元素。
    • 使用 window.getComputedStyle 方法获取每个元素的计算样式。
    • 处理 CSS 样式,包括背景、边框、阴影、字体、文本对齐等。
  3. 处理特殊元素:
    • 图像:加载图像资源,处理跨域图像问题(CORS)。
    • SVG:解析和绘制 SVG 元素。
    • 伪元素:处理 ::before 和 ::after 伪元素,将它们视为普通 DOM 元素进行绘制。
  4. 创建 Canvas 元素:
    • 创建一个新的 <canvas> 元素,获取其 2D 绘图上下文(context)。
    • 根据目标元素的尺寸调整 Canvas 的宽度和高度。
  5. 绘制背景和边框:
    • 根据元素的背景颜色、背景图像、背景渐变等属性,绘制背景。
    • 绘制元素的边框,包括边框样式、宽度和颜色。
  6. 绘制文本:
    • 处理字体样式(如字体家族、字号、字体颜色、行高等)。
    • 使用 fillText 方法在 Canvas 上绘制文本内容。
  7. 绘制内容:
    • 绘制图像内容,包括处理图像的缩放和定位。
    • 处理其他内容元素,如内联 SVG 和 Canvas 元素本身。
  8. 处理叠加效果:
    • 处理元素的阴影效果(box-shadow 和 text-shadow)。
    • 处理混合模式和透明度。
  9. 递归绘制子元素:
    • 递归遍历和绘制元素的子节点,确保所有子元素都按照正确的层级关系绘制。
    • 使用深度优先搜索(DFS)方法确保绘制顺序正确。
  10. 绘制伪元素:处理和绘制元素的伪元素(::before 和 ::after),确保它们与普通元素一样正确绘制。
  11. 处理滚动和视口:
    • 处理滚动视图,确保只绘制当前可见区域。
    • 处理固定定位和绝对定位的元素。
  12. 处理剪切和蒙版:
    • 处理元素的剪切路径(clip-path)
    • 处理蒙版效果(mask)
  13. 最终生成 Canvas:将所有解析和绘制操作完成后,生成最终的 Canvas 图像。

下面是一个简单的概念性的示意代码:

function convertToCanvas(element, options) {
    // 1. 初始化与配置
    const config = mergeDefaultOptions(options);

    // 2. 解析和计算样式
    const nodeParser = new NodeParser(element, config);
    const parsedNodes = nodeParser.parse();

    // 3. 创建 Canvas 元素
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    // 4. 递归绘制元素
    parsedNodes.forEach(node => {
        drawNode(context, node, config);
    });

    // 5. 返回生成的 Canvas
    return canvas;
}

function drawNode(context, node, config) {
    // 绘制背景
    drawBackground(context, node, config);

    // 绘制边框
    drawBorders(context, node, config);

    // 绘制内容
    drawContent(context, node, config);

    // 递归绘制子元素
    node.children.forEach(child => {
        drawNode(context, child, config);
    });
}

function drawBackground(context, node, config) {
    // 绘制背景颜色、图像等
}

function drawBorders(context, node, config) {
    // 绘制边框
}

function drawContent(context, node, config) {
    // 绘制文本、图像等内容
}

// ....

不过好在前端已经有了这样的库:html2canvas

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>HTML to Canvas</title>
  </head>
  <body>
    <div id="content">
      <h1>Hello, World!</h1>
      <p>This is a simple example of converting HTML to Canvas.</p>
    </div>
    <button id="convert">Convert to Canvas</button>
    <div id="canvas-container"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.5.0-beta4/html2canvas.min.js"></script>
    <script>
      document.getElementById("convert").addEventListener("click", function () {
        const content = document.getElementById("content");

        html2canvas(content)
          .then((canvas) => {
            // 将生成的 Canvas 添加到页面
            document.getElementById("canvas-container").appendChild(canvas);
          })
          .catch((error) => {
            console.error("Error converting HTML to Canvas:", error);
          });
      });
    </script>
  </body>
</html>

2. 将 Canvas 转为 Blob

Clipboard API 提供的 ClipboardItem 接口,并不支持直接将 base64 字符串写入,而是需要转换为 Blob 对象,之后再进一步封装为 ClipboardItem 后通过 navigator.clipboard.write 方法将其写入剪贴板。

示例代码如下:

async function copyBase64ToClipboard(base64Data) {
    const blob = new Blob([base64Data], { type: 'image/png' }); // 这里假设 base64Data 是一个 PNG 图片的 base64 字符串
    const clipboardItem = new ClipboardItem({ 'image/png': blob });
    await navigator.clipboard.write([clipboardItem]);
}

注意:Blob 并不是流数据,但是它可以和流数据相互转换。Blob 对象表示一段不可变的原始数据,而流数据表示一个可以按需逐步读取或写入的数据序列。

Blob 的特性:

  • Blob 是不可变的:一旦创建,Blob 的内容就不能被改变。
  • Blob 可以表示二进制数据:例如文件数据、图像数据等。
  • Blob 可以通过 slice 方法进行分割,生成新的 Blob 对象。

与流的转换:

  • Blob 可以与流数据进行转换。例如,可以将 Blob 转换为 ReadableStream,以便逐步读取数据。
  • 通过 Response 对象的 body 属性,可以将 Blob 包装为 ReadableStream:
const blob = new Blob(["Hello, world!"], { type: "text/plain" });

// 将 Blob 转换为流
const stream = blob.stream();

// 读取流数据
const reader = stream.getReader();
reader.read().then(({ done, value }) => {
  if (done) {
    console.log("Stream reading complete");
  } else {
    console.log("Stream data:", new TextDecoder().decode(value));
  }
});

回到我们的代码里面:

function copyDivToImage() {
  const el = document.getElementById("target"); // 拿到 DOM 元素
  html2canvas(el).then((canvas) => {
    // 转换为 canvas
    // 调用 toBlob 转为 Blob 数据
    canvas.toBlob(
      (blob) => {
        // 复制文件到剪贴板
      },
      "image/jpeg", // 文件的格式
      1 // 图像压缩质量 0-1
    );
  });
}

得到 Blob 数据之后,就是将 Blob 数据放入到剪贴板里面:

function copyDivToImage() {
  const el = document.getElementById("target");
  html2canvas(el).then((canvas) => {
    canvas.toBlob(
      async (blob) => {
        // 复制文件到剪贴板
        try {
          await navigator.clipboard.write([
            new ClipboardItem({
              [blob.type]: blob,
            }),
          ]);
          console.log("图像已成功复制到剪贴板");
        } catch (err) {
          console.error("无法复制图像到剪贴板", err);
        }
      },
      "image/jpeg", // 文件的格式
      1 // 图像压缩质量 0-1
    );
  });
}

3. 常见问题

  1. HTTP 环境下 clipboard 无法正常工作

navigator.clipboard 需要在 HTTPS 环境下才能正常工作。在 HTTP 环境下,Clipboard API 将无法使用。

文档地址:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API

  1. 剪贴板对 JPEG 格式的图片支持比较差

浏览器在 Clipboard API 中通常支持的 MIME 类型包括 text/plain、text/html 和 image/png。这些是规范中明确规定必须支持的类型,而其他类型如 image/jpeg 可能并不一致地受到所有浏览器的支持。

因此我们还需要对上面的代码做参数调整:

function copyDivToImage() {
  const el = document.getElementById("target");
  html2canvas(el).then((canvas) => {
    canvas.toBlob(
      async (blob) => {
        // 复制文件到剪贴板
        try {
          await navigator.clipboard.write([
            new ClipboardItem({
              [blob.type]: blob,
            }),
          ]);
          console.log("图像已成功复制到剪贴板");
        } catch (err) {
          console.error("无法复制图像到剪贴板", err);
        }
      },
      "image/png", // 文件的格式
      1 // 图像压缩质量 0-1
    );
  });
}

-EOF-

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式