ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

iPhone,iPadでPDFが1枚目しか表示されない問題の解決策

 

iframeでPDFを表示すると、iPhoneやiPadでは1枚目しか表示されない問題がある。これはstackoverflowや他のサイトでも報告されているとおりブラウザのバグっぽい。

iPhoneだとPDFの1枚目しか表示されない

PCだと1枚目以降も表示される

対応策

  1. PDF.jsを利用する
  2. BLOLオブジェクトURLで表示する
  3. 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

例: https://docs.google.com/viewer?url=https://www.kansaigaidai.ac.jp/asp/img/pdf/82/7a79c35f7ce0704dec63be82440c8182.pdf&embedded=true

めちゃくちゃ参考になったサイト

www.ottorinobruni.com

こちらのサイトにBLOBオブジェクトでの解決策が載っていて本当に助かりました。

処理の流れとしては

  1. PDFをbase64文字列に変換
  2. ブラウザでbase64文字列をデコード
  3. 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

iPhoneでも1枚目以降が表示できた!

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

終わりに

色々調べてみると下記のサイトがとても丁寧にまとめてくださっていました。

developer.kaizenplatform.com