Web URL 中神奇的 createObjectURL method

tags: javascript,typescript,createObjectURL

大家好,我是 Johnny,今天這篇是最近我在公司內部分享的內容,也分享給大家~主要是介紹一個好用的 Web URL API - createObjectURL

什麼是 createObjectURL

  • 建立一個帶有 URL 的 DOMString 以代表參數中所傳入的物件
  • 生命週期與創造它的 window 中的 document 相同
  • 此物件 URL 代表了所指定的 File 物件、 Blob 物件「參考」
  1. 每次呼叫 createObjectURL 都會產生一個新的 URL
  2. 雖然在 window unload 時瀏覽器會自動釋放記憶體,但建議當物件不再被使用時,手動透過 URL.revokeObjectURL() 釋放記憶體,藉此最佳化效能與記憶體用量,具體 revoke 時機可參考這篇open in new window

基本用法

// create url "reference" to the fileObject
const url = URL.createObjectURL(fileObject);
// revoke url
URL.revokeObjectURL(url);

應用 1. 圖片上傳預覽

const App = () => {
  const [previewImage, setPreviewImage] = useState();

  const onFileChange = (e) => {
      // ...
  };

  return (
    <>
      <input type="file" onChange={onFileChange} />
      <br />
      {previewImage && <img src={previewImage} />}
    </>
  );
};

FileReader

  • need to create FileReader instance
  • slow, need to read the whole file as a base64 data URL
  • more memory cost
const App = () => {
  const [previewImage, setPreviewImage] = useState();

  const onFileChange = (e) => {
    const reader = new FileReader();
    reader.onload = () => {
      // data:image/jpeg;base64,.....
      setPreviewImage(reader.result);
    };
    reader.readAsDataURL(e.target.files[0]);
  };

  // ...
};

createObjectURL

  • no need to create anything
  • fast, just a reference url to file
  • low memory usage
const App = () => {
  const [previewImage, setPreviewImage] = useState();

  const onFileChange = (e) => {
    const url = URL.createObjectURL(e.target.files[0]);
    setPreviewImage(url);
  };
  
  // ...
};

應用 2. Canvas 轉存下載(僅示範)

const App = () => {
  const canvasRef = useRef(null);

  const download = (url) => {
    let a = document.createElement('a');
    a.setAttribute('href', url);
    a.setAttribute('download', 'test');
    a.click();
  };

  const onDownload = (e) => {
     // ...
  };

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.font = "30px Arial";
    ctx.strokeText("Hello World", 10, 50);
  }, []);

  return (
    <>
      <button onClick={onDownload}>Preview</button>
      <div>
        <h5>Canvas:</h5>
        <canvas ref={canvasRef} />
      </div>
    </>
  );
};

toDataURL

const App = () => {
  const canvasRef = useRef(null);

  const onPreview = () => {
    const url = canvasRef.current.toDataURL('image/png', 1.0);
    download(url);
  };

  // ...
};

createObjectURL

const App = () => {
  const canvasRef = useRef(null);

  const onPreview = () => {
    canvasRef.current.toBlob((blob) => {
      download(URL.createObjectURL(blob));
    });
  };

  // ...
};

應用 3. 即時編譯

// index.js
export const sum = (a, b) => {
  return a + b;
};

export default {
  msg: 'hello world'
};
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="my-module" id="entry" src="index.js"></script>
<script>
  // get our module
  const rawScript = document.querySelector('#entry');
  const url = rawScript.getAttribute('src');

  // request for content
  const request = new XMLHttpRequest();
  request.open('GET', url, false);
  request.send(null);
  
  // compile content
  const { code } = Babel.transform(request.responseText, {
    presets: [
      Babel.availablePresets.react,
    ],
    filename: url,
  });

  // module
  const myModule = new Blob([code], {
    type: 'text/javascript',
  });
  const blobUrl = URL.createObjectURL(myModule);

  // replace script
  const myScript = document.createElement('script');
  myScript.type = 'module';
  myScript.src = blobUrl;
  rawScript.parentNode.replaceChild(myScript, rawScript);
</script>

<script type="module">
  (async () => {
    const { sum } = (await import(blobUrl)).default;
    document.getElementById('output').innerHTML = `1 + 1 = ${sum(1, 1)}`;
  })();
</script>
Last Updated:
Contributors: johnnywang