前端二进制数据指南:从 ArrayBuffer 到高级流处理

news2025/7/18 8:08:19

前端开发中,二进制数据是处理文件、图像、音视频、网络通信等场景的基础。以下是核心概念和用途的通俗解释:

前端二进制数据介绍

1. 什么是前端二进制数据?

指计算机原始的 0 和 1 格式的数据(比如一张图片的底层代码),前端通过特定 API 操作这些数据。


2. 核心用途

  • 文件操作
    上传/下载文件(如用户传图片)、切片大文件(断点续传)。

  • 图像处理
    修改图片像素(滤镜、裁剪)、Canvas 绘图。

  • 网络通信
    WebSocket 传输二进制帧、AJAX 上传二进制数据。

  • 高性能计算
    WebAssembly 运行编译型代码(如游戏、加密算法)。


3. 关键 API 速览

API作用示例场景
ArrayBuffer存储原始二进制数据文件解析、网络数据接收
Uint8Array按字节操作数据(8位无符号整数)图像像素处理
Blob表示文件类数据(不可变)文件分片上传
FileReader读取本地文件内容图片预览(转Base64)
Canvas操作图像像素数据实时滤镜效果

4. 为什么需要二进制操作?

  • 性能更高:直接操作内存,比文本处理快。

  • 功能更强:实现文件编辑、音视频解码等复杂功能。

  • 节省带宽:二进制比文本格式(如JSON)体积更小。

前端二进制数据核心类型对比表

类型/API继承关系使用场景核心优势
ArrayBuffer基础容器,无继承关系存储原始二进制数据,如网络传输、文件解析、WebAssembly 内存操作直接操作内存,性能极高
TypedArray基于 ArrayBuffer 的视图(如 Uint8Array类型化数据处理(图像像素、音频采样、网络协议解析)类型明确,自动处理字节对齐,简化操作
DataView基于 ArrayBuffer 的视图处理混合数据类型或需要控制字节序的场景(如解析二进制文件格式、网络协议)灵活读写不同数据类型,支持手动控制字节序
Blob无继承关系处理不可变的类文件数据(文件分片上传、生成临时 URL)支持大文件分片(Blob.slice()),可直接用于 fetch 或 FormData
File继承自 Blob表示用户上传的本地文件(通过 <input type="file"> 获取)包含文件名、类型、修改时间等元信息
FileReader无继承关系异步读取 Blob 或 File 内容(转为 ArrayBufferDataURL、文本等)支持多种读取格式,兼容浏览器文件操作
ImageData包含 Uint8ClampedArray操作 Canvas 像素数据(图像滤镜、特效处理)直接映射到 Canvas 像素,RGBA 格式标准化
Streams API基于 ReadableStream/WritableStream流式处理大文件(如视频分块加载、实时数据传输)分块处理数据,避免内存溢出,提升性能
WebAssembly通过 ArrayBuffer 加载模块高性能计算(游戏、音视频编码、密码学算法)接近原生代码性能,支持 C/C++/Rust 编译运行

二进制数据核心类型关键关联说明

  1. 继承链

    • File → Blob → 所有文件相关操作基于 Blob 扩展。

    • TypedArray/DataView → ArrayBuffer → 所有类型化操作依赖原始二进制缓冲区。

  2. 协作流程示例

    • 文件上传
      File → FileReader → ArrayBuffer → TypedArray(处理数据) → fetch 上传。

    • 图像处理
      Blob → Image → Canvas → ImageData(像素操作) → 显示结果。

    • WebAssembly
      .wasm → ArrayBuffer → 编译实例化 → 调用高性能函数。

 快速对比示例

// 1. Blob → ArrayBuffer → TypedArray
const blob = new Blob([new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f])]); // "Hello"
blob.arrayBuffer().then(buffer => {
  const uint8 = new Uint8Array(buffer);
  console.log(uint8); // Uint8Array(5) [72, 101, 108, 108, 111]
});

// 2. File → FileReader → ArrayBuffer
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();
  reader.onload = () => {
    const buffer = reader.result;
    const dataView = new DataView(buffer);
    console.log("文件头:", dataView.getUint32(0));
  };
  reader.readAsArrayBuffer(file);
});

二进制数据核心类型总结

  • 底层核心ArrayBuffer 是二进制操作的基础,其他类型均围绕它扩展。

  • 文件处理Blob 和 File 用于文件级操作,FileReader 提供读取能力。

  • 数据视图TypedArray 和 DataView 提供灵活的内存操作方式。

  • 高性能场景WebAssembly 和 Streams API 解决计算和流式处理需求。

前端常用二进制数据介绍

1. ArrayBuffer

作用:最基础的二进制数据容器,表示通用的、固定长度的原始二进制数据缓冲区,不能直接操作,需通过 TypedArray 或 DataView 访问
优势:直接操作内存,性能极高,适合处理音频、视频等二进制数据。
使用场景:网络传输、文件解析、WebAssembly 内存操作。

区分:Buffer是Node.js 特有的二进制类型(等同于 Uint8Array),前端通常通过浏览器 API 或工具库(如 buffer polyfill)使用

<!DOCTYPE html>
<html>
<head>
  <title>ArrayBuffer 示例</title>
</head>
<body>
  <script>
    // 1. 创建一个 16 字节的 ArrayBuffer
    const buffer = new ArrayBuffer(16);

    // 2. 使用 TypedArray 操作数据
    const int32View = new Int32Array(buffer); // 每个元素占 4 字节
    int32View[0] = 42;                        // 写入数据
    int32View[1] = 1024;

    // 3. 通过另一个视图验证数据
    const uint8View = new Uint8Array(buffer);
    console.log("ArrayBuffer 内容(十六进制):");
    for (let i = 0; i < uint8View.length; i++) {
      console.log(uint8View[i].toString(16).padStart(2, '0'));
    }
  </script>
</body>
</html>

2. TypedArray

作用

  • 基于 ArrayBuffer 的视图,提供特定类型的二进制数据操作,包括:

    • Int8ArrayUint8Array(无符号字节)

    • Uint8ClampedArray(限制在 0-255,常用于图像处理)

    • Int16ArrayUint16Array

    • Int32ArrayUint32Array

    • Float32ArrayFloat64Array

    • BigInt64ArrayBigUint64Array(ES2020 引入,用于大整数)

优势:类型明确,无需手动处理字节细节。
使用场景:图像像素操作、音频数据处理。

<!DOCTYPE html>
<html>
<head>
  <title>TypedArray 示例</title>
</head>
<body>
  <script>
    // 1. 创建一个 4 字节的 ArrayBuffer
    const buffer = new ArrayBuffer(4);

    // 2. 使用不同类型视图操作同一 buffer
    const uint8View = new Uint8Array(buffer);
    uint8View[0] = 0x12;  // 写入字节数据
    uint8View[1] = 0x34;

    const uint16View = new Uint16Array(buffer);
    console.log("Uint16 视图的值:", uint16View[0].toString(16)); // 输出 3412(小端序)

    const uint32View = new Uint32Array(buffer);
    console.log("Uint32 视图的值:", uint32View[0].toString(16)); // 输出 34120000
  </script>
</body>
</html>

3. DataView

作用:灵活读写 ArrayBuffer,支持混合数据类型和字节序控制。
优势:处理复杂二进制格式时更灵活。
使用场景:解析自定义协议、处理网络数据包

<!DOCTYPE html>
<html>
<head>
  <title>DataView 示例</title>
</head>
<body>
  <script>
    // 1. 创建一个 8 字节的 ArrayBuffer
    const buffer = new ArrayBuffer(8);
    const view = new DataView(buffer);

    // 2. 写入不同数据类型(大端序)
    view.setInt32(0, 0x12345678, false);  // 大端序写入 4 字节整数
    view.setFloat32(4, Math.PI, false);   // 大端序写入浮点数

    // 3. 读取数据
    console.log("整数:", view.getInt32(0, false).toString(16));
    console.log("浮点数:", view.getFloat32(4, false));
  </script>
</body>
</html>

4. Blob (Binary Large Object)

作用:不可变的类文件对象,常用于文件切片(slice())、下载或上传(如 fetch 的 FormData)
优势:适合处理大文件,可直接用于网络请求。
使用场景:文件下载、图片预览。

<!DOCTYPE html>
<html>
<body>
  <button onclick="downloadFile()">下载 Blob 文件</button>
  <script>
    function downloadFile() {
      // 1. 创建 Blob 内容
      const text = "Hello, Blob!";
      const blob = new Blob([text], { type: 'text/plain' });

      // 2. 生成临时 URL 并下载
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'example.txt';
      a.click();
      URL.revokeObjectURL(url);
    }
  </script>
</body>
</html>

5. File 和 FileReader

File作用:读取用户本地文件内容,继承自 Blob,表示用户本地文件(通过 <input type="file"> 获取),包含文件名、类型等元数据

FileReader作用:用于异步读取 Blob 或 File 内容为 ArrayBufferDataURL 或文本
优势:直接获取文件元数据,支持异步读取。
使用场景:文件上传、图片预览。

<!DOCTYPE html>
<html>
<body>
  <input type="file" id="fileInput">
  <div id="output"></div>
  <script>
    document.getElementById('fileInput').addEventListener('change', function(e) {
      const file = e.target.files[0];
      const reader = new FileReader();

      // 1. 读取为文本
      reader.readAsText(file);
      reader.onload = () => {
        document.getElementById('output').innerHTML = `
          <p>文件名: ${file.name}</p>
          <p>大小: ${file.size} 字节</p>
          <p>内容: ${reader.result}</p>
        `;
      };
    });
  </script>
</body>
</html>

6. ImageData 和 Canvas

作用:操作 Canvas 的像素数据。
优势:直接修改图像像素,实现实时效果。
使用场景:图像滤镜、实时视频处理。

<!DOCTYPE html>
<html>
<body>
  <canvas id="canvas" width="200" height="200"></canvas>
  <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 1. 绘制原始图形
    ctx.fillStyle = 'red';
    ctx.fillRect(0, 0, 200, 200);

    // 2. 获取像素数据并反色
    const imageData = ctx.getImageData(0, 0, 200, 200);
    const data = imageData.data;
    for (let i = 0; i < data.length; i += 4) {
      data[i] = 255 - data[i];     // R
      data[i + 1] = 255 - data[i + 1]; // G
      data[i + 2] = 255 - data[i + 2]; // B
    }

    // 3. 写回修改后的像素
    ctx.putImageData(imageData, 0, 0);
  </script>
</body>
</html>

7. Streams API

作用:分块处理大型数据流,避免内存溢出。
优势:高效处理大文件,实时数据传输。
使用场景:视频流、大文件上传/下载。

<!DOCTYPE html>
<html>
<body>
  <script>
    // 1. 创建一个简单的 ReadableStream
    const stream = new ReadableStream({
      start(controller) {
        controller.enqueue(new Uint8Array([72, 101, 108, 108, 111])); // "Hello"
        controller.close();
      }
    });

    // 2. 通过 Reader 读取数据块
    const reader = stream.getReader();
    reader.read().then(({ done, value }) => {
      if (!done) {
        console.log("流数据:", new TextDecoder().decode(value));
      }
    });
  </script>
</body>
</html>

8. WebAssembly

作用:运行高性能编译型代码(如 C/C++)。
优势:接近原生性能,适合计算密集型任务。
使用场景:游戏、音视频编码、加密算法。

<!DOCTYPE html>
<html>
<body>
  <script>
    // 假设存在 add.wasm 文件,导出一个加法函数
    fetch('add.wasm')
      .then(response => response.arrayBuffer())
      .then(buffer => WebAssembly.instantiate(buffer))
      .then(({ instance }) => {
        console.log("WASM 加法结果:", instance.exports.add(2, 3));
      });
  </script>
</body>
</html>

常用的二进制数据转换函数

1. 将 File 对象转换为 Blob 对象

/**
 * 将 File 对象转换为 Blob 对象(File 继承自 Blob,可直接切片)
 * @param {File} file - 用户上传的 File 对象
 * @returns {Blob} - 转换后的 Blob 对象
 */
function fileToBlob(file) {
  // File 本身是 Blob 的子类,直接使用 slice 方法切割完整文件
  return file.slice(0, file.size, file.type);
}

// 示例用法
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  const blob = fileToBlob(file);
  console.log('File -> Blob:', blob);
});

2. 将 Blob 对象转换为 File 对象

/**
 * 将 Blob 转换为 File 对象(添加文件名和类型)
 * @param {Blob} blob - 需要转换的 Blob 对象
 * @param {string} fileName - 文件名(含扩展名,如 "image.jpg")
 * @param {string} [fileType] - 文件类型(如 "image/jpeg",默认使用 Blob 的 type)
 * @returns {File} - 转换后的 File 对象
 */
function blobToFile(blob, fileName, fileType) {
  // 保留原始 Blob 的类型(若未指定 fileType)
  const type = fileType || blob.type;
  // 使用 File 构造函数创建对象(兼容性良好)
  return new File([blob], fileName, { type });
}

// 示例用法
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const file = blobToFile(blob, 'example.txt');
console.log('Blob -> File:', file);

3. 将 Blob 转换为 ArrayBuffer

/**
 * 异步将 Blob 转换为 ArrayBuffer
 * @param {Blob} blob - 需要转换的 Blob 对象
 * @returns {Promise<ArrayBuffer>} - 返回 Promise 包含 ArrayBuffer
 */
function blobToArrayBuffer(blob) {
  return new Promise((resolve, reject) => {
    // 使用 FileReader 读取 Blob
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result); // 读取完成返回 ArrayBuffer
    reader.onerror = () => reject(reader.error);  // 处理错误
    reader.readAsArrayBuffer(blob); // 关键 API 调用
  });
}

// 示例用法
const blob = new Blob(['Hello']);
blobToArrayBuffer(blob).then(buffer => {
  console.log('Blob -> ArrayBuffer:', buffer);
});

4. 将 ArrayBuffer 转换为 Blob

/**
 * 将 ArrayBuffer 转换为 Blob 对象
 * @param {ArrayBuffer} buffer - 需要转换的 ArrayBuffer
 * @param {string} [type] - Blob 的 MIME 类型(如 "image/png")
 * @returns {Blob} - 转换后的 Blob
 */
function arrayBufferToBlob(buffer, type) {
  // 直接通过 Blob 构造函数转换
  return new Blob([buffer], { type: type || '' });
}

// 示例用法
const buffer = new Uint8Array([72, 101, 108, 108, 111]).buffer;
const blob = arrayBufferToBlob(buffer, 'text/plain');
console.log('ArrayBuffer -> Blob:', blob);

5. 将 Blob 转换为 Base64 字符串

/**
 * 异步将 Blob 转换为 Base64 字符串
 * @param {Blob} blob - 需要转换的 Blob
 * @returns {Promise<string>} - 返回 Promise 包含 Base64 字符串
 */
function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      // 去除 DataURL 前缀(如 "data:text/plain;base64,")
      const base64 = reader.result.split(',')[1];
      resolve(base64);
    };
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(blob); // 读取为 DataURL
  });
}

// 示例用法
const blob = new Blob(['Hello']);
blobToBase64(blob).then(base64 => {
  console.log('Blob -> Base64:', base64); // 输出 "SGVsbG8="
});

6. 将 Base64 字符串转换为 Blob

/**
 * 将 Base64 字符串转换为 Blob 对象
 * @param {string} base64 - Base64 字符串
 * @param {string} [type] - Blob 的 MIME 类型(如 "image/png")
 * @returns {Blob} - 转换后的 Blob
 */
function base64ToBlob(base64, type) {
  // 将 Base64 转换为二进制字符串
  const byteString = atob(base64);
  // 创建 Uint8Array 视图
  const buffer = new Uint8Array(byteString.length);
  for (let i = 0; i < byteString.length; i++) {
    buffer[i] = byteString.charCodeAt(i);
  }
  // 生成 Blob
  return new Blob([buffer], { type: type || '' });
}

// 示例用法
const base64 = 'SGVsbG8='; // "Hello" 的 Base64
const blob = base64ToBlob(base64, 'text/plain');
console.log('Base64 -> Blob:', blob);

7. 将 Blob 转换为 DataURL

/**
 * 异步将 Blob 转换为 DataURL(可直接用于 img.src)
 * @param {Blob} blob - 需要转换的 Blob
 * @returns {Promise<string>} - 返回 Promise 包含 DataURL
 */
function blobToDataURL(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result); // 直接返回完整 DataURL
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(blob);
  });
}

// 示例用法(图片预览)
const blob = new Blob([new Uint8Array([0x89, 0x50, 0x4E, 0x47])], { type: 'image/png' });
blobToDataURL(blob).then(dataURL => {
  console.log('Blob -> DataURL:', dataURL); // 输出 "data:image/png;base64,..."
});

8. 将 Blob 转换为 Object URL

/**
 * 将 Blob 转换为 Object URL(需手动释放内存)
 * @param {Blob} blob - 需要转换的 Blob
 * @returns {string} - Object URL(如 "blob:http://...")
 */
function blobToObjectURL(blob) {
  // 使用 URL.createObjectURL 生成临时链接
  return URL.createObjectURL(blob);
}

// 示例用法
const blob = new Blob(['Hello']);
const url = blobToObjectURL(blob);
console.log('Blob -> Object URL:', url);

// 使用后需释放内存!
// URL.revokeObjectURL(url);

9. 将 Blob 转换为 Uint8Array

/**
 * 异步将 Blob 转换为 Uint8Array
 * @param {Blob} blob - 需要转换的 Blob
 * @returns {Promise<Uint8Array>} - 返回 Promise 包含 Uint8Array
 */
function blobToUint8Array(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      // 通过 ArrayBuffer 创建 Uint8Array
      const buffer = reader.result;
      resolve(new Uint8Array(buffer));
    };
    reader.onerror = () => reject(reader.error);
    reader.readAsArrayBuffer(blob);
  });
}

// 示例用法
const blob = new Blob(['Hello']);
blobToUint8Array(blob).then(uint8Array => {
  console.log('Blob -> Uint8Array:', uint8Array); // 输出 [72, 101, 108, 108, 111]
});

10. 将 Blob 转换为文本

/**
 * 异步将 Blob 转换为文本内容
 * @param {Blob} blob - 需要转换的 Blob
 * @param {string} [encoding] - 文本编码(默认 "utf-8")
 * @returns {Promise<string>} - 返回 Promise 包含文本内容
 */
function blobToText(blob, encoding = 'utf-8') {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsText(blob, encoding); // 指定编码读取
  });
}

// 示例用法
const blob = new Blob(['你好']);
blobToText(blob).then(text => {
  console.log('Blob -> Text:', text); // 输出 "你好"
});

11.使用总结

  • 通用逻辑:大部分转换依赖 FileReader 和 Blob 构造函数。

  • 关键点

    • FileReader 的 readAsXXX 方法决定读取格式。

    • Blob 的 type 属性影响生成文件的 MIME 类型。

    • Object URL 使用后需调用 URL.revokeObjectURL() 释放内存。

  • 错误处理:所有异步函数均返回 Promise,建议用 try/catch 包裹。

完整的图片上传案例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片上传与居中全屏预览</title>
    <style>
        /* 基础样式重置 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        /* 页面主体样式 */
        body {
            font-family: Arial, sans-serif;
            min-height: 100vh;
            background: #f5f5f5;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }

        /* 上传容器样式 */
        .upload-container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            text-align: center;
        }

        /* 自定义上传按钮 */
        .custom-upload-btn {
            background: #2196F3;
            color: white;
            padding: 12px 24px;
            border-radius: 5px;
            cursor: pointer;
            transition: background 0.3s;
            display: inline-block;
        }

        .custom-upload-btn:hover {
            background: #1976D2;
        }

        /* 隐藏原生文件输入 */
        #fileInput {
            display: none;
        }

        /* 错误提示样式 */
        .error-message {
            color: #ff4444;
            margin-top: 10px;
            display: none;
        }

        /* 预览容器样式 */
        #previewContainer {
            max-width: 90%;
            margin: 20px 0;
            cursor: zoom-in;
            position: relative;
            display: none; /* 默认隐藏 */
        }

        /* 预览图片样式 */
        #previewImage {
            max-width: 100%;
            max-height: 70vh;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            transition: transform 0.3s;
        }

        /* 全屏模式样式 */
        #previewContainer.fullscreen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0, 0, 0, 0.9);
            z-index: 9999;
            cursor: zoom-out;
            display: flex !important; /* 强制覆盖默认样式 */
            align-items: center; /* 垂直居中 */
            justify-content: center; /* 水平居中 */
        }

        /* 全屏时图片样式 */
        #previewContainer.fullscreen #previewImage {
            max-width: 90vw;
            max-height: 90vh;
            object-fit: contain; /* 保持比例填充 */
        }
    </style>
</head>
<body>
<!-- 上传区域 -->
<div class="upload-container">
    <label class="custom-upload-btn" for="fileInput">
        📷 点击上传图片
    </label>
    <input type="file" id="fileInput" accept="image/*">
    <div class="error-message" id="errorMessage">只能上传图片文件 (JPEG/PNG/GIF)</div>
</div>

<!-- 图片预览区域 -->
<div id="previewContainer">
    <img id="previewImage" alt="图片预览">
</div>

<script>
    // =====================
    // 元素获取
    // =====================
    const fileInput = document.getElementById('fileInput');
    const previewImage = document.getElementById('previewImage');
    const previewContainer = document.getElementById('previewContainer');
    const errorMessage = document.getElementById('errorMessage');

    // =====================
    // 文件上传处理
    // =====================
    fileInput.addEventListener('change', function(e) {
        const file = e.target.files[0];

        // 清除错误提示
        errorMessage.style.display = 'none';

        if (!file) return;

        // 验证文件类型
        if (!file.type.startsWith('image/')) {
            showError("文件类型错误,请选择图片");
            this.value = ''; // 清空选择
            return;
        }

        // 创建文件阅读器
        const reader = new FileReader();

        // 文件读取成功回调
        reader.onload = function(e) {
            previewImage.src = e.target.result;
            previewContainer.style.display = 'block'; // 显示预览容器
        };

        // 错误处理
        reader.onerror = function() {
            showError("文件读取失败");
            previewContainer.style.display = 'none';
        };

        // 开始读取文件
        reader.readAsDataURL(file);
    });

    // =====================
    // 全屏功能
    // =====================
    previewContainer.addEventListener('click', toggleFullscreen);

    // 全屏切换函数
    function toggleFullscreen() {
        if (document.fullscreenElement) {
            exitFullscreen();
        } else {
            enterFullscreen(previewContainer);
        }
    }

    // 进入全屏
    function enterFullscreen(element) {
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.mozRequestFullScreen) { // Firefox
            element.mozRequestFullScreen();
        } else if (element.webkitRequestFullscreen) { // Chrome/Safari
            element.webkitRequestFullscreen();
        } else if (element.msRequestFullscreen) { // IE/Edge
            element.msRequestFullscreen();
        }

        // 添加全屏类名
        element.classList.add('fullscreen');
    }

    // 退出全屏
    function exitFullscreen() {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.mozCancelFullScreen) { // Firefox
            document.mozCancelFullScreen();
        } else if (document.webkitExitFullscreen) { // Chrome/Safari
            document.webkitExitFullscreen();
        } else if (document.msExitFullscreen) { // IE/Edge
            document.msExitFullscreen();
        }

        // 移除全屏类名
        previewContainer.classList.remove('fullscreen');
    }

    // 监听全屏状态变化
    document.addEventListener('fullscreenchange', () => {
        previewContainer.classList.toggle('fullscreen', !!document.fullscreenElement);
    });

    // =====================
    // 辅助函数
    // =====================
    function showError(message) {
        errorMessage.textContent = message;
        errorMessage.style.display = 'block';
        previewContainer.style.display = 'none';
    }
</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2378981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何选择高性价比的 1T 服务器租用服务​

选择高性价比的 1T 服务器租用服务​&#xff0c;可参考以下内容&#xff1a; 1、根据需求选配置​ 明确自身业务需求是关键。若为小型网站或轻量级应用&#xff0c;数据存储与处理需求不高&#xff0c;选择基础配置服务器即可。如个人博客网站&#xff0c;普通的 Intel Xeon …

一个可拖拉实现列表排序的WPF开源控件

从零学习构建一个完整的系统 推荐一个可通过拖拉&#xff0c;来实现列表元素的排序的WPF控件。 项目简介 gong-wpf-dragdrop是一个开源的.NET项目&#xff0c;用于在WPF应用程序中实现拖放功能&#xff0c;可以让开发人员快速、简单的实现拖放的操作功能。 可以在同一控件内…

AI-02a5a6.神经网络-与学习相关的技巧-批量归一化

批量归一化 Batch Normalization 设置合适的权重初始值&#xff0c;则各层的激活值分布会有适当的广度&#xff0c;从而可以顺利的进行学习。那么&#xff0c;更进一步&#xff0c;强制性的调整激活值的分布&#xff0c;是的各层拥有适当的广度呢&#xff1f;批量归一化&#…

SVGPlay:一次 CodeBuddy 主动构建的动画工具之旅

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 背景与想法 我一直对 SVG 图标的动画处理有浓厚兴趣&#xff0c;特别是描边、渐变、交互等效果能为图标增添许…

自己手写tomcat项目

一&#xff1a;Servlet的原理 在Servlet(接口中)有&#xff1a; 1.init():初始化servlet 2.getServletConfig()&#xff1a;获取当前servlet的配置信息 3.service():服务器&#xff08;在HttpServlet中实现&#xff0c;目的是为了更好的匹配http的请求方式&#xff09; 4.g…

2025年渗透测试面试题总结-安恒[实习]安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 安恒[实习]安全工程师 一面 1. 自我介绍 2. 前两段实习做了些什么 3. 中等难度的算法题 4. Java的C…

生成对抗网络(Generative Adversarial Networks ,GAN)

生成对抗网络是深度学习领域最具革命性的生成模型之一。 一 GAN框架 1.1组成 构造生成器&#xff08;G&#xff09;与判别器&#xff08;D&#xff09;进行动态对抗&#xff0c;实现数据的无监督生成。 G&#xff08;造假者&#xff09;&#xff1a;接收噪声 ​&#xff0c…

在WSL中的Ubuntu发行版上安装Anaconda、CUDA、CUDNN和TensorRT

在Windows 11的WSL&#xff08;Windows Subsystem for Linux&#xff09;环境中安装Anaconda、CUDA、CUDNN和TensorRT的详细步骤整理&#xff1a; 本文是用cuda12.4与CuDNN 8.9.7 和 TensorRT 9.1.0 及以上对应 一、前言&#xff08;准备&#xff09; 确保电脑上有NVIDIA GPU…

代码随想录算法训练营第60期第三十九天打卡

大家好&#xff0c;我们今天继续讲解我们的动态规划章节&#xff0c;昨天我们讲到了动态规划章节的背包问题&#xff0c;昨天讲解的主要是0-1背包问题&#xff0c;那么今天我们可能就会涉及到完全背包问题&#xff0c;昨天的题目有一道叫做分割等和子集&#xff0c;今天应该会有…

计算机网络体系结构深度解析:从理论到实践的全面梳理

计算机网络体系结构深度解析&#xff1a;从理论到实践的全面梳理 本系列博客源自作者在大二期末复习计算机网络时所记录笔记&#xff0c;看的视频资料是B站湖科大教书匠的计算机网络微课堂&#xff0c;祝愿大家期末都能考一个好成绩&#xff01; 一、常见计算机网络体系结构 …

【教程】Docker更换存储位置

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 背景说明 更换教程 1. 停止 Docker 服务 2. 创建新的存储目录 3. 编辑 Docker 配置文件 4. 迁移已有数据到新位置 5. 启动 Docker 服务 6…

鸿蒙Next API17学习新特性之组件可见区域变化事件新增支持设置事件的回调参数,限制它的执行间隔

概述 鸿蒙开发文档更新的非常快&#xff0c;对应我们开发者的学习能力也要求非常高&#xff0c;今天这篇文章给大家分享一下鸿蒙API17中更新的新特性学习。 鸿蒙 Next 的组件可见区域变化事件在最新的 API Version 17 中得到了增强&#xff0c;新增了支持设置事件的回调参数的…

深入解析 React 的 useEffect:从入门到实战

文章目录 前言一、为什么需要 useEffect&#xff1f;核心作用&#xff1a; 二、useEffect 的基础用法1. 基本语法2. 依赖项数组的作用 三、依赖项数组演示1. 空数组 []&#xff1a;2.无依赖项&#xff08;空&#xff09;3.有依赖项 四、清理副作用函数实战案例演示1. 清除定时器…

通过Ollama读取模型

通过Ollama读取模型 前言一、查看本地Ollama上有哪些模型二、调用bge-m3模型1、调用模型2、使用bge-m3进行相似度比较 三、调用大模型 前言 手动下载和加载大模型通常需要复杂的环境配置&#xff0c;而使用Ollama可以避免这一问题。本文将介绍如何调用Ollama上的模型。 一、查…

永久免费,特殊版本!

随着大家审美的不断提升&#xff0c;无论是社交平台的日常分享还是特定场景的图像展示&#xff0c;人们对图像质量的要求都日益严苛。为了呈现更完美的视觉效果&#xff0c;许多小伙伴都会对原始图像进行精细化的后期处理&#xff0c;其中复杂背景抠图、光影调整、色彩校正等专…

Canva 推出自有应用生成器以与 Bolt 和 Lovable 竞争

AI 目前是一个巨大的市场,每个人都想从中分一杯羹。 即使是 Canva,这个以拖放图形设计而闻名的流行设计平台,也在其 Canva Create 2025 活动中发布了自己版本的代码生成器,加入了 AI 竞赛。 但为什么一个以设计为先的平台会提供代码生成工具呢? 乍看之下,这似乎有些不…

Matrix-Game:键鼠实时控制、实时生成的游戏生成模型(论文代码详细解读)

1.简介 本文介绍了一种名为Matrix-Game的交互式世界基础模型&#xff0c;专门用于可控的游戏世界生成。 Matrix-Game通过一个两阶段的训练流程来实现&#xff1a;首先进行大规模无标签预训练以理解环境&#xff0c;然后进行动作标记训练以生成交互式视频。为此&#xff0c;研…

MySQL 5.7在CentOS 7.9系统下的安装(下)——给MySQL设置密码

新下载下来的MySQL&#xff0c;由于没有root密码&#xff0c;&#xff08;1&#xff09;所以如果我们希望登陆mysql&#xff0c;得给mysql的root账户设置密码&#xff0c;或者另一方面来说&#xff0c;&#xff08;2&#xff09;未来如果你忘记root密码了&#xff0c;也能通过这…

机器学习笔记2

5 TfidfVectorizer TF-IDF文本特征词的重要程度特征提取 (1) 算法 词频(Term Frequency, TF), 表示一个词在当前篇文章中的重要性 逆文档频率(Inverse Document Frequency, IDF), 反映了词在整个文档集合中的稀有程度 (2) API sklearn.feature_extraction.text.TfidfVector…

重排序模型解读 mxbai-rerank-base-v2 强大的重排序模型

mxbai-rerank-base-v2 强大的重排序模型 模型介绍benchmark综合评价安装 模型介绍 mxbai-rerank-base-v2 是 Mixedbread 提供的一个强大的重排序模型&#xff0c;旨在提高搜索相关性。该模型支持多语言&#xff0c;特别是在英语和中文方面表现出色。它还支持代码和 SQL 排序&a…