base64与图片的转换和预览(高阶玩法)

1.完整的功能描述

功能概述

这是一个网页工具,支持用户输入不同格式的图片数据或上传本地图片文件,对图片进行预览、转换为多种格式,并支持导出不同格式的图片数据。

输入方式

  1. 文本输入 :用户可以输入 Data URL、公网图片 URL、Blob URL 或 Hex 字符串。支持的格式包括 data:image/...;base64 、 http(s)://xxx.jpg 、 blob:... 以及纯 Hex 字符串。

  2. 文件上传 :用户也能选择上传本地图片文件,支持常见的图片格式。 核心功能

  3. 预览功能 :用户点击"预览"按钮后,工具会根据输入内容尝试加载并显示图片。如果输入是网络图片,会处理 CORS 问题;若输入为 Hex 字符串,会将其转换为图片。

  4. 转换功能 :点击"转换"按钮,工具会将输入的图片数据转换为 Data URL、Blob 对象信息、ArrayBuffer(十进制字节数组)和 Hex 字符串,并显示在对应的文本框中。

  5. 导出功能 :

  • 图片导出 :用户可以选择导出图片的格式(PNG、JPEG、WebP、GIF),点击"导出图片"按钮即可保存图片。

  • 数据导出 :支持导出 Data URL、Blob 对象信息、ArrayBuffer 和 Hex 字符串,点击对应按钮可保存相应数据。 错误处理

当输入格式不支持、图片加载失败或转换出错时,工具会显示错误信息提示用户。同时,在加载过程中会显示加载状态,提升用户体验。

2.完整样式展示

3.完整代码演示

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>图片 ↔ 多格式预览、转换 & 导出</title>
  <style>
    :root {
      --primary: #5c6bc0;
      --light: #f5f5f5;
      --dark: #424242;
      --accent: #ff7043;
      --radius: 8px;
      --button-hover: #ff7043;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: "Helvetica Neue", Arial, sans-serif;
      background-color: var(--light);
      color: var(--dark);
      display: flex;
      justify-content: center;
      padding: 40px 0;
    }
    .container {
      width: 90%;
      max-width: 800px;
      background-color: #fff;
      border-radius: var(--radius);
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
      padding: 24px;
    }
    h1 {
      margin-bottom: 24px;
      text-align: center;
      color: var(--primary);
    }
    .note {
      color: #e53935;
      font-size: 0.9rem;
      margin-bottom: 16px;
    }
    .section {
      margin-bottom: 24px;
    }
    .section label {
      display: block;
      margin-bottom: 8px;
      font-weight: bold;
    }
    textarea, select, input[type="text"] {
      width: 100%;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: var(--radius);
      font-family: monospace;
      background: #fafafa;
      resize: vertical;
    }
    input[type="file"] {
      display: block;
      margin-top: 4px;
    }
    .buttons, .export-section {
      text-align: center;
      margin-top: 8px;
    }
    .buttons button, .export-btn {
      background: var(--primary);
      color: #fff;
      border: none;
      padding: 6px 12px;
      border-radius: var(--radius);
      cursor: pointer;
      font-size: 0.9rem;
      transition: background 0.3s;
      margin: 4px;
    }
    .buttons button:hover, .export-btn:hover {
      background: var(--button-hover);
    }
    #preview {
      display: block;
      max-width: 100%;
      border-radius: var(--radius);
      margin: 12px auto;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    textarea[readonly] {
      background: #eee;
    }
    .field-with-btn {
      position: relative;
    }
    .field-with-btn textarea {
      margin-bottom: 8px;
    }
    .field-with-btn .export-btn {
      position: absolute;
      top: 8px;
      right: 8px;
    }
    .format-select-wrapper {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 8px;
    }
    .format-select-wrapper select {
      width: auto;
    }
    .error-message {
      color: #e53935;
      font-size: 0.9rem;
      margin-top: 8px;
      display: none;
    }
    .loading {
      position: relative;
      min-height: 100px;
    }
    .loading::after {
      content: "🔄 加载中...";
      display: block;
      text-align: center;
      color: var(--primary);
      padding: 20px;
    }
  </style>
</head>
<body>
<div class="container">
  <img src="data:image/png;base64,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" alt="Logo">
  <h1>图片 ↔ 多格式预览、转换 & 导出</h1>
  <div class="note">
    ⚠️ 请确保输入的是<b>图片文件的 URL</b>(例如 .jpg/.png/.webp 等),否则无法预览或转换。
    <br>⚠️ 注意:网络图片需要支持CORS才能正常加载
  </div>

  <!-- Input and File Upload -->
  <div class="section">
    <label for="textInput">输入 Data URL、公网图片 URL、Blob URL 或 Hex 字符串</label>
    <textarea id="textInput" rows="3" placeholder="支持 data:image/...;base64, http(s)://xxx.jpg, blob:..., 纯 Hex"></textarea>
    <div id="inputError" class="error-message"></div>
  </div>
  <div class="section">
    <label for="fileInput">或 上传 本地文件</label>
    <input type="file" id="fileInput" accept="image/*">
  </div>

  <div class="buttons">
    <button id="previewBtn">预览</button>
    <button id="convertBtn">转换</button>
    <button id="clearBtn" style="background:#999;">清空</button>
  </div>

  <!-- Image Preview -->
  <div class="section loading" id="previewContainer">
    <label>图片预览</label>
    <img id="preview" alt="预览图像" style="display:none;" crossorigin="anonymous">
  </div>

  <!-- Format Selection and Export -->
  <div class="section export-section">
    <label>导出预览图像</label><br>
    <div class="format-select-wrapper">
      <select id="formatSelect">
        <option value="image/png">PNG</option>
        <option value="image/jpeg">JPEG</option>
        <option value="image/webp">WebP</option>
        <option value="image/gif">GIF</option>
      </select>
      <button id="exportImageBtn" class="export-btn">导出图片</button>
    </div>
  </div>

  <!-- Data URL Export -->
  <div class="section field-with-btn">
    <label for="outDataUrl">Data URL(Base64)</label>
    <textarea id="outDataUrl" rows="2" readonly></textarea>
    <button class="export-btn" id="exportDataUrlBtn">导出 Data URL</button>
  </div>

  <!-- Blob Export -->
  <div class="section field-with-btn">
    <label>Blob 对象 信息</label>
    <textarea id="outBlob" rows="2" readonly placeholder="显示 Blob 的 size/type"></textarea>
    <button class="export-btn" id="exportBlobInfoBtn">导出 Blob 信息</button>
  </div>

  <!-- ArrayBuffer Export -->
  <div class="section field-with-btn">
    <label for="outArrayBuffer">ArrayBuffer(十进制字节数组)</label>
    <textarea id="outArrayBuffer" rows="3" readonly></textarea>
    <button class="export-btn" id="exportArrayBufferBtn">导出 ArrayBuffer</button>
  </div>

  <!-- Hex Export -->
  <div class="section field-with-btn">
    <label for="outHex">Hex 字符串</label>
    <textarea id="outHex" rows="3" readonly></textarea>
    <button class="export-btn" id="exportHexBtn">导出 Hex</button>
  </div>
</div>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    // DOM元素引用
    const elements = {
      textInput: document.getElementById('textInput'),
      fileInput: document.getElementById('fileInput'),
      previewBtn: document.getElementById('previewBtn'),
      convertBtn: document.getElementById('convertBtn'),
      clearBtn: document.getElementById('clearBtn'),
      preview: document.getElementById('preview'),
      formatSelect: document.getElementById('formatSelect'),
      exportImageBtn: document.getElementById('exportImageBtn'),
      outDataUrl: document.getElementById('outDataUrl'),
      outBlob: document.getElementById('outBlob'),
      outArrayBuffer: document.getElementById('outArrayBuffer'),
      outHex: document.getElementById('outHex'),
      previewContainer: document.getElementById('previewContainer'),
      inputError: document.getElementById('inputError')
    };

    let currentBlob = null;
    let currentDataUrl = null;

    // 显示错误信息
    function showError(message) {
      elements.inputError.textContent = message;
      elements.inputError.style.display = 'block';
      elements.previewContainer.classList.remove('loading');
    }

    // 隐藏错误信息
    function hideError() {
      elements.inputError.style.display = 'none';
    }

    // 重置功能
    function resetAll() {
      elements.textInput.value = '';
      elements.fileInput.value = '';
      elements.preview.style.display = 'none';
      elements.outDataUrl.value = '';
      elements.outBlob.value = '';
      elements.outArrayBuffer.value = '';
      elements.outHex.value = '';
      currentBlob = null;
      currentDataUrl = null;
      hideError();
      elements.previewContainer.classList.remove('loading');
      if (elements.preview.src.startsWith('blob:')) {
        URL.revokeObjectURL(elements.preview.src);
      }
    }

    // 处理CORS代理请求
    async function fetchWithCORS(url) {
      try {
        const response = await fetch(url, {
          mode: 'cors',
          credentials: 'omit'
        });

        if (!response.ok) {
          throw new Error('无法加载图片');
        }

        return response;
      } catch (error) {
        throw new Error(`无法加载图片: ${error.message}`);
      }
    }

    // 预览功能
    async function previewInput() {
      try {
        elements.previewContainer.classList.add('loading');
        hideError();

        const input = elements.textInput.value.trim();
        const file = elements.fileInput.files[0];

        if (input) {
          if (/^data:image\//i.test(input)) { // Data URL
            currentDataUrl = input;
            const img = new Image();
            img.onload = async () => {
              elements.preview.src = currentDataUrl;
              elements.preview.style.display = 'block';
              elements.previewContainer.classList.remove('loading');
              // 调用转换功能以更新数据显示
              await convertInput();
            };
            img.onerror = () => {
              throw new Error('无效的Data URL图片');
            };
            img.src = currentDataUrl;
          } else if (/^https?:\/\//i.test(input)) { // 网络URL
            const img = new Image();
            img.crossOrigin = "Anonymous";
            img.onload = async () => {
              elements.preview.src = input;
              elements.preview.style.display = 'block';
              elements.previewContainer.classList.remove('loading');
              // 调用转换功能以更新数据显示
              await convertInput();
            };
            img.onerror = async () => {
              try {
                const response = await fetchWithCORS(input);
                const blob = await response.blob();
                const url = URL.createObjectURL(blob);
                elements.preview.src = url;
                elements.preview.style.display = 'block';
                elements.previewContainer.classList.remove('loading');
                // 调用转换功能以更新数据显示
                await convertInput();
              } catch (error) {
                throw new Error('图片加载失败');
              }
            };
            img.src = input;
          } else if (/^blob:/i.test(input)) { // Blob URL
            elements.preview.src = input;
            elements.preview.style.display = 'block';
            elements.previewContainer.classList.remove('loading');
            // 调用转换功能以更新数据显示
            await convertInput();
          } else if (/^[0-9a-f\s]+$/i.test(input)) { // Hex字符串
            // 直接调用转换功能处理Hex字符串
            await convertInput();
          } else {
            throw new Error('不支持的输入格式');
          }
        } else if (file) { // 本地文件
          const url = URL.createObjectURL(file);
          elements.preview.src = url;
          elements.preview.style.display = 'block';
          elements.previewContainer.classList.remove('loading');
          // 调用转换功能以更新数据显示
          await convertInput();
        } else {
          throw new Error('请输入内容或上传文件');
        }
      } catch (error) {
        console.error('预览错误:', error);
        showError(`预览错误: ${error.message}`);
        elements.preview.style.display = 'none';
        elements.previewContainer.classList.remove('loading');
      }
    }

    // 转换功能
    async function convertInput() {
      try {
        elements.previewContainer.classList.add('loading');
        hideError();

        const input = elements.textInput.value.trim();
        const file = elements.fileInput.files[0];
        let blob, buffer;

        if (input) {
          if (/^data:image\//i.test(input)) { // Data URL
            currentDataUrl = input;
            blob = await fetch(currentDataUrl).then(r => r.blob());
          } else if (/^https?:\/\//i.test(input)) { // 网络URL
            const response = await fetchWithCORS(input);
            blob = await response.blob();
            // 验证图片类型
            if (!blob.type.startsWith('image/')) {
              throw new Error('URL指向的不是图片文件');
            }
          } else if (/^blob:/i.test(input)) { // Blob URL
            const response = await fetch(input);
            blob = await response.blob();
          } else if (/^[0-9a-f\s]+$/i.test(input)) { // Hex字符串
            let cleanHex = input.replace(/\s+/g, '');
            if (cleanHex.length % 2 !== 0) throw new Error('Hex长度无效');
            buffer = new Uint8Array(cleanHex.match(/../g).map(h => parseInt(h, 16)));
            blob = new Blob([buffer], { type: 'image/*' });
          } else {
            throw new Error('不支持的输入格式');
          }
        } else if (file) { // 本地文件
          blob = file;
        } else {
          throw new Error('请输入内容或上传文件');
        }

        if (!blob) {
          throw new Error('无法创建Blob对象');
        }

        // 更新数据展示
        currentBlob = blob;
        buffer = await blob.arrayBuffer();

        // 生成Data URL
        currentDataUrl = await blobToDataURL(blob);
        elements.preview.src = currentDataUrl;
        elements.preview.style.display = 'block';

        elements.outDataUrl.value = currentDataUrl;
        elements.outBlob.value = `size: ${blob.size} bytes\ntype: ${blob.type || '未知类型'}`;

        const uint8 = new Uint8Array(buffer);
        elements.outArrayBuffer.value = JSON.stringify([...uint8]);
        elements.outHex.value = [...uint8].map(b => b.toString(16).padStart(2, '0')).join('');

        elements.previewContainer.classList.remove('loading');
      } catch (error) {
        console.error('转换错误:', error);
        showError(`转换错误: ${error.message}`);
        elements.preview.style.display = 'none';
        elements.previewContainer.classList.remove('loading');
      }
    }

    // 工具函数:Blob转DataURL
    function blobToDataURL(blob) {
      return new Promise((resolve) => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
      });
    }

    // 导出图片功能
    elements.exportImageBtn.addEventListener('click', async () => {
      if (!currentDataUrl) {
        showError('请先转换图片');
        return;
      }

      try {
        const img = new Image();
        img.crossOrigin = "Anonymous";
        img.src = currentDataUrl;

        await new Promise((resolve, reject) => {
          img.onload = resolve;
          img.onerror = () => reject(new Error('图片加载失败'));
        });

        const canvas = document.createElement('canvas');
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);

        const type = elements.formatSelect.value;
        canvas.toBlob(blob => {
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = `image_export.${type.split('/')[1] || 'png'}`;
          a.click();
          setTimeout(() => {
            URL.revokeObjectURL(url);
            URL.revokeObjectURL(img.src);
          }, 1000);
        }, type, 0.9);

      } catch (error) {
        console.error('导出图片错误:', error);
        showError(`导出图片错误: ${error.message}`);
      }
    });

    // 通用导出处理
    function setupExportButton(buttonId, contentFieldId, filename) {
      const button = document.getElementById(buttonId);
      const contentField = document.getElementById(contentFieldId);

      button.addEventListener('click', () => {
        const content = contentField.value;
        if (!content) {
          showError('无可用数据');
          return;
        }
        const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        setTimeout(() => URL.revokeObjectURL(url), 1000);
      });
    }

    // 绑定导出功能
    setupExportButton('exportDataUrlBtn', 'outDataUrl', 'dataurl.txt');
    setupExportButton('exportBlobInfoBtn', 'outBlob', 'blob_info.txt');
    setupExportButton('exportArrayBufferBtn', 'outArrayBuffer', 'arraybuffer.json');
    setupExportButton('exportHexBtn', 'outHex', 'hex.txt');

    // 事件监听
    elements.previewBtn.addEventListener('click', previewInput);
    elements.convertBtn.addEventListener('click', convertInput);
    elements.clearBtn.addEventListener('click', resetAll);
    elements.textInput.addEventListener('input', hideError);
    elements.fileInput.addEventListener('change', hideError);
  });
</script>
</body>
</html>

<style>
    :root {
      --primary: #5c6bc0;
      --light: #f5f5f5;
      --dark: #424242;
      --accent: #ff7043;
      --radius: 12px;
      --button-hover: #ff7043;
      --transition: all 0.3s ease;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: "Helvetica Neue", Arial, sans-serif;
      background-color: var(--light);
      color: var(--dark);
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      padding: 20px;
    }
    .container {
      width: 90%;
      max-width: 1000px;
      background-color: #fff;
      border-radius: var(--radius);
      box-shadow: 0 6px 30px rgba(0, 0, 0, 0.1);
      padding: 32px;
      transition: var(--transition);
    }
    .container:hover {
      box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
    }
    h1 {
      margin-bottom: 32px;
      text-align: center;
      color: var(--primary);
      font-size: 2.2rem;
    }
    .note {
      color: #e53935;
      font-size: 1rem;
      margin-bottom: 24px;
      padding: 12px;
      background-color: #ffebee;
      border-radius: var(--radius);
    }
    .section {
      margin-bottom: 32px;
      padding: 16px;
      background-color: #fafafa;
      border-radius: var(--radius);
      transition: var(--transition);
    }
    .section:hover {
      background-color: #f5f5f5;
    }
    .section label {
      display: block;
      margin-bottom: 12px;
      font-weight: bold;
      color: var(--primary);
    }
    textarea, select, input[type="text"] {
      width: 100%;
      padding: 14px;
      border: 1px solid #ddd;
      border-radius: var(--radius);
      font-family: monospace;
      background: #fff;
      resize: vertical;
      transition: var(--transition);
    }
    textarea:focus, select:focus, input[type="text"]:focus {
      border-color: var(--primary);
      outline: none;
      box-shadow: 0 0 0 3px rgba(92, 107, 192, 0.2);
    }
    input[type="file"] {
      display: block;
      margin-top: 8px;
      padding: 8px 0;
    }
    .buttons, .export-section {
      text-align: center;
      margin-top: 16px;
    }
    .buttons button, .export-btn {
      background: var(--primary);
      color: #fff;
      border: none;
      padding: 10px 20px;
      border-radius: var(--radius);
      cursor: pointer;
      font-size: 1rem;
      transition: var(--transition);
      margin: 6px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    .buttons button:hover, .export-btn:hover {
      background: var(--button-hover);
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
    #preview {
      display: block;
      max-width: 100%;
      border-radius: var(--radius);
      margin: 16px auto;
      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
      transition: var(--transition);
    }
    #preview:hover {
      transform: scale(1.02);
    }
    textarea[readonly] {
      background: #f8f8f8;
      color: #666;
    }
    .field-with-btn {
      position: relative;
    }
    .field-with-btn textarea {
      margin-bottom: 12px;
    }
    .field-with-btn .export-btn {
      position: absolute;
      top: 12px;
      right: 12px;
      padding: 6px 12px;
      font-size: 0.9rem;
    }
    .format-select-wrapper {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 12px;
    }
    .format-select-wrapper select {
      width: auto;
      min-width: 120px;
    }
    .error-message {
      color: #e53935;
      font-size: 1rem;
      margin-top: 12px;
      display: none;
      padding: 8px;
      background-color: #ffebee;
      border-radius: var(--radius);
    }
    .loading {
      position: relative;
      min-height: 150px;
    }
    .loading::after {
      content: "🔄 加载中...";
      display: block;
      text-align: center;
      color: var(--primary);
      padding: 30px;
      font-size: 1.2rem;
    }
  </style>
相关推荐
草巾冒小子1 分钟前
vue3中解决 return‘ inside ‘finally‘ block报错的问题
前端·javascript·vue.js
zfyljx25 分钟前
五子棋html
前端·css·html
MossGrower31 分钟前
65.Three.js案例-使用 MeshNormalMaterial 和 MeshDepthMaterial 创建 3D 图形
javascript·threejs·spheregeometry·torusknotgeome
仰望星空的凡人2 小时前
【JS逆向基础】前端基础-HTML与CSS
css·python·html·js逆向
pianmian15 小时前
坐标系与坐标系数转换
vue.js·html
-Camellia007-5 小时前
TypeScript学习案例(1)——贪吃蛇
javascript·学习·typescript
GoFly开发者6 小时前
GoFly企业版框架升级2.6.6版本说明(框架在2025-05-06发布了)
前端·javascript·vue.js
GanGuaGua7 小时前
CSS:元素显示模式与背景
前端·javascript·css·html
一个会的不多的人7 小时前
C# NX二次开发:判断两个体是否干涉和获取系统日志的UFUN函数
前端·javascript·html