iframeでPDFを表示すると、iPhoneやiPadでは1枚目しか表示されない問題がある。これはstackoverflowや他のサイトでも報告されているとおりブラウザのバグっぽい。
対応策
- PDF.jsを利用する
- BLOLオブジェクトURLで表示する
- Google Docs Viewerを利用する
1のPDF.jsはいろいろなサイトで紹介されているのでそちらで。今回は「2. BLOLオブジェクトURLで表示する」方法を紹介します。
一応「3. Google Docs Viewerを利用する」についても軽く紹介しておくと、下記のようなURLにすればPDFが表示できます。
https://docs.google.com/viewer?url={PDFのURL}&embedded=true
めちゃくちゃ参考になったサイト
こちらのサイトにBLOBオブジェクトでの解決策が載っていて本当に助かりました。
処理の流れとしては
- PDFをbase64文字列に変換
- ブラウザでbase64文字列をデコード
- Blobオブジェクトを生成しファイルURLを取得、ブラウザで開く
です。
コード
自分の環境ではNext.jsでページを作っていたので、サーバーサイドでPDFの取得・base64化してクライアントでデコードしてBlobオブジェクトを作成しています。
pages/index.tsx
// サーバーサイド import { GetServerSideProps } from 'next' import fetch from 'node-fetch' export const getServerSideProps: GetServerSideProps = async (ctx) => { // PDFを取得し、base64化 const pdf = ( await fetch('https://www.africau.edu/images/default/sample.pdf').then( (res) => res.buffer() ) ).toString('base64') return { props: { pdf, }, } } // クライアント import { useEffect } from 'react' interface Props { pdf: string } const IndexPage = ({ pdf }) => { useEffect(() => { const binary = atob(pdf.replace(/\s/g, '')) const len = binary.length const buffer = new ArrayBuffer(len) const view = new Uint8Array(buffer) for (let i = 0; i < len; i++) { view[i] = binary.charCodeAt(i) } const blob = new Blob([view], { type: 'application/pdf' }) const url = URL.createObjectURL(blob) window.location.replace(url) setTimeout(() => { URL.revokeObjectURL(url) }, 500) }, []) return <iframe src={pdf} width="100%" height="100%" /> } export default IndexPage
Blobオブジェクトのデメリット
BLOLオブジェクトURLで表示するデメリットとしては下記があります。
- 画面リロードすると表示できなくなる
- URLが
blob:http://localhost:3000/48699860-f1f4-4a06-b76d-2156392c582e
のようになり、このURLを他の人が見てもアクセスできない - Blobはメモリに溜まるので
URL.revokeObjectURL(url)
で明示的に解放する必要がある
このような制約があるのは、Blobがブラウザにデータが保存される仕様のためです。Blobについては下記がわかりやすかったです。
https://developer.mozilla.org/ja/docs/Web/API/Blob https://ja.javascript.info/blob
終わりに
色々調べてみると下記のサイトがとても丁寧にまとめてくださっていました。