JavaScript
HTML5
canvas
es6
pdf.js

JavaScriptでPDFのサムネイル画像を作成しアップロードする

JavaScriptで画像をリサイズしてから画像をアップロードする

以前作成した上記のものにPDF対応にしたものを作成してみました。

  • PDF.jsを使用しています。
  • サーバーサイドの処理は前回から変更していません。

サーバーサイドは無いので失敗しますが、以下のような感じになります。
(CSRFの処理はコメントアウトしています。)
https://jsfiddle.net/bfb4b39z/41/

コメントで指摘いただいたものを参考に更新しました。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>
<script type="text/javascript" src="https://mozilla.github.io/pdf.js/build/pdf.worker.js"></script>
<script>
let scaleSlider = null;
let scaleValue = null;
let canvas = null;
let context = null;
let image = null;
const reader = new FileReader();
let fixFileObject = null;
let scale = null;
let messageArea = null;
let pdfFlag = false;
const pdfImageExtention = "png";
const csrf_token_name = "{$csrf_token_name}";
let csrf_hash = "{$csrf_hash}";

document.addEventListener("DOMContentLoaded", function() {
    canvas = document.getElementById("thumbnail");
    context = canvas.getContext("2d");

    scaleSlider = document.getElementById("scale");
    scaleValue = document.getElementById("scale-value");

    messageArea = document.getElementById("message")

    scaleSlider.addEventListener("change", changeScale);

    setFileEventListenner();
});

function changeScale() {
    scaleValue.value = scaleSlider.value;
    drawCanvas();
}

function setFileEventListenner() {
    document.getElementById("file-selecter").addEventListener("change", createPreview);
}

function createPreview(event) {

    fixFileObject = null;

    let fileObject = event.target.files[0];

    if (typeof fileObject === "undefined") {
        return;
    }

    if (fileObject.type.match(/^(image\/jpeg|image\/png|application\/pdf)$/) === null) {
        // jpegとpngとpdf以外の場合はクリアして終了
        let fileArea = document.getElementById("file-input");
        fileArea.innerHTML = fileArea.innerHTML;
        setFileEventListenner();
        return;
    }

    pdfFlag = fileObject.type.match(/^application\/pdf$/) !== null;

    fixFileObject = fileObject;

    image = new Image();

    reader.onload = function(event) {
        if ( ! pdfFlag) {
            image.src = event.target.result;// base64
        } else {
          pdfjsLib.getDocument(new Uint8Array(event.target.result))
                .then(pdf => {
                    return pdf.getPage(1);
                })
                .then(page => {
                    canvas.style.display = "none";// 読み込み時に下記のサイズで描画されるのでdrawCanvas()まで非表示にしておく
                    // ここで基準となるcanvasサイズが決まるのでscaleは1で読み込んでおく
                    let viewport = page.getViewport(1);
                    canvas.height = viewport.height;
                    canvas.width = viewport.width;

                    let renderContext = {
                        canvasContext: context,
                        viewport: viewport
                    };

                    // Render PDF page and set image
                    return page.render(renderContext);// Promise
                })
                .then(() => {
                    return new Promise(resolve => {
                        canvas.toBlob(blob => {
                            resolve(blob);
                        }, "image/" + pdfImageExtention);
                    });
                })
                .then(blob => {
                    if(image.src) {
                        const oldSrc = image.src;
                        URL.revokeObjectURL(oldSrc);// createObjectURLで作成したURLを削除
                    }
                    image.src = URL.createObjectURL(blob);
                })
                .catch(error => {
                    messageArea.innerHTML = '<span style="color: red;">ファイルの読み込みに失敗しました。</span>';
                });
        }
    }

    image.onload = function() {
        drawCanvas();
    }

    pdfFlag ? reader.readAsArrayBuffer(fileObject) : reader.readAsDataURL(fileObject);
}

function drawCanvas() {
    scale = scaleSlider.value;
    if (image !== null) {
        let imageWidth = parseInt(image.width * scale, 10);
        let imageHeight = parseInt(image.height * scale, 10);
        canvas.width = imageWidth;
        canvas.height = imageHeight;
        context.clearRect(0, 0, imageWidth, imageHeight);
        context.drawImage(image, 0, 0, imageWidth, imageHeight);
    }
    if (canvas.style.display === "none")
    {
        canvas.style.display = "inherit";
    }
}

// awaitを使うのでasync宣言
async function submitResizeFile() {
    if (image !== null && fixFileObject !== null) {

        let fileType = pdfFlag ? "image/" + pdfImageExtention : fixFileObject.type;
        let fileName = pdfFlag ? fixFileObject.name.replace(/pdf/g, pdfImageExtention) : fixFileObject.name;

        // toBlobが非同期処理なのでawaitでPromise待ち
        let resizeFileObject = await (() => {
            return new Promise(resolve => {
                if (scale != 1 || pdfFlag === true) {
                    // 画像ファイルでサイズ変更があった場合またはPDFファイルの場合に送信用ファイルを作成
                    canvas.toBlob(blob => {
                        resolve(blob);
                    }, fileType);
                } else {
                    // 画像ファイルでサイズ変更がない場合はそのまま
                    resolve(fixFileObject);
                }
            });
        })();

        let formData = new FormData();
        formData.append(csrf_token_name, csrf_hash);
        formData.append("file", resizeFileObject, fileName);

        // input type file の 値はjavascriptで上書き出来ないのでajaxで送信する
        fetch("/root/execute_upload", {
            method: "POST",
            body: formData,
            credentials: "same-origin",// send Cookie csrf_token
        }).then(response => {
            // Forbidden is not json
            if (response.status !== 403) {
                response.json().then(function(json) {
                    csrf_hash = json.csrf_hash;
                });
            }
            if (response.ok) {
                messageArea.innerHTML = '<span style="color: green;">ファイルの送信に成功しました。</span>';
            } else {
                messageArea.innerHTML = '<span style="color: red;">ファイルの送信に失敗しました。</span>';
            }
        });
    }
}
</script>
</head>
<body>
<form id="form">
    Scale : <input id="scale" type="range" step="0.1" min="0.1" max="1" value="1"><input disabled id="scale-value" type="text" value="1" style="width: 2rem;">
    <div id="file-input"><input id="file-selecter" type="file" name="file" accept="image/jpeg, image/png, application/pdf"></div>
    <div><canvas id="thumbnail" style="display: none;"></canvas></div>
    <input type="button" value="送信" onclick="submitResizeFile()">
    <div id="message"></div>
</form>
</body>
</html>