質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PDF

PDF(Portable Document Format)とはISOによって国際標準として制定されている電子ドキュメント用の拡張子です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

1回答

281閲覧

JavaScriptでつくったJPEGとPDFコンバータが動作しない

kofun

総合スコア12

PDF

PDF(Portable Document Format)とはISOによって国際標準として制定されている電子ドキュメント用の拡張子です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

1クリップ

投稿2024/03/28 10:10

アプリの概要

HTML,JavaScriptでPDFにJPEGを変換する。ページ順は画像アップロード時にアップロードする場所(列)で決まる。その列は追加・削除可能。出力DPIをしていするフォームがある。そこに、自動入力ボタンが押されたらその時点で選択されているJPEGの中で最低のDPIを探し、それを自動入力する。変換ボタンが押されたらPDFに変換し、ダウンロードする。

実現したいこと

このアプリで、動かない問題を解決し、ページサイズがLetterになってしまう問題も解決したいです。

エラーメッセージ(開発者ツールに表示されているもの)

Form elements must have labels: Element has no title attribute Element has no placeholder attribute →影響を受けるソース:<input type="file" accept="image/jpeg" onchange="updateDPI(this)">、<input type="file" accept="image/jpeg" onchange="updateDPI(this)">

これ以外は表示されていません(Edge⦅Windows11⦆の開発者ツール)。

該当のソースコード

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JPEG to PDFコンバータ</title> <style> .overlayL { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(0, 0, 0, 0.5); display: none; justify-content: center; align-items: center; } .loaderL { display: flex; justify-content: center; align-items: center; } </style> </head> <body> <table id="imageTable"> <thead> <tr> <th>ページ数</th> <th>JPEGアップロード</th> <th>削除</th> </tr> </thead> <tbody> <tr> <td>1</td> <td><input type="file" accept="image/jpeg" onchange="updateDPI(this)" /></td> <td><button onclick="deleteRow(this)">列削除</button></td> </tr> <tr> <td>2</td> <td><input type="file" accept="image/jpeg" onchange="updateDPI(this)" /></td> <td><button onclick="deleteRow(this)">列削除</button></td> </tr> </tbody> </table> <button onclick="addRow()">列追加</button> <button onclick="convertToPDF()">変換</button> <form id="dpiForm"> <label for="dpi">DPI:</label> <input type="number" id="dpi" name="dpi" min="1" step="1" required> <button type="button" onclick="autoFillDPI()">DPI自動入力</button> </form> <div class="overlayL" id="overlayL"> <div class="loaderL"> <img src="loading.svg" alt="処理中..." width="52px"> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.2/html2canvas.min.js"></script> <script> 'use strict'; function addRow() { const table = document.getElementById("imageTable").getElementsByTagName('tbody')[0]; const rowCount = table.rows.length; const row = table.insertRow(rowCount); const pageNumber = rowCount + 1; const cell1 = row.insertCell(0); const cell2 = row.insertCell(1); const cell3 = row.insertCell(2); cell1.innerHTML = pageNumber; cell2.innerHTML = '<input type="file" accept="image/jpeg" onchange="updateDPI(this)" />'; cell3.innerHTML = '<button onclick="deleteRow(this)">列削除</button>'; } function deleteRow(button) { const row = button.parentNode.parentNode; row.parentNode.removeChild(row); } function updateDPI(input) { const file = input.files[0]; if (!file || !file.type.startsWith('image/jpeg')) { alert('JPEGファイルを選択してください'); return; } const image = new Image(); const reader = new FileReader(); reader.onload = function(e) { image.src = e.target.result; image.onload = function() { const dpi = (image.width / file.width) * 72; // 1px = 1/72 inchと仮定 input.parentNode.parentNode.querySelector('input[type="number"]').value = dpi; }; }; reader.readAsDataURL(file); } function autoFillDPI() { const table = document.getElementById("imageTable").getElementsByTagName('tbody')[0]; const rows = table.getElementsByTagName('tr'); let minDPI = Infinity; for (let i = 0; i < rows.length; i++) { const input = rows[i].getElementsByTagName('input')[0]; if (input && input.files.length > 0) { const file = input.files[0]; const image = new Image(); const reader = new FileReader(); reader.onload = function(e) { image.src = e.target.result; image.onload = function() { const dpi = (image.width / file.width) * 72; if (dpi < minDPI) { minDPI = dpi; } }; }; reader.readAsDataURL(file); } } if (minDPI === Infinity) { alert('画像ファイルが選択されていません'); return; } document.getElementById('dpi').value = minDPI; } async function convertToPDF() { const dpi = document.getElementById('dpi').value; if (!dpi) { alert('DPI値を入力してください'); return; } const table = document.getElementById("imageTable").getElementsByTagName('tbody')[0]; const rows = table.getElementsByTagName('tr'); // ローディングを表示 document.getElementById('overlayL').style.display = 'flex'; const canvasPromises = []; for (let i = 0; i < rows.length; i++) { const input = rows[i].getElementsByTagName('input')[0]; if (input && input.files.length > 0) { const file = input.files[0]; const canvasPromise = createCanvasFromImage(file); canvasPromises.push(canvasPromise); } } Promise.all(canvasPromises) .then(async canvases => { const pdf = new jsPDF({ orientation: 'p', unit: 'in', format: [8.5, 11] // ここ:Letterサイズならこうするが、A4,B5,A5,フリーサイズにするにはどうすればいいのか }); for (const canvas of canvases) { pdf.setDPI(dpi); pdf.addImage(canvas.toDataURL('image/jpeg'), 'JPEG', 0, 0, 8.5, 11); if (canvas !== canvases[canvases.length - 1]) { pdf.addPage(); } } const pdfDataUri = pdf.output('datauristring'); downloadPDF(pdfDataUri); }) .catch(error => { console.error('Error converting images to PDF:', error); }) .finally(() => { // 処理が完了したらローディングを非表示に document.getElementById('overlayL').style.display = 'none'; }); } function createCanvasFromImage(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); resolve(canvas); } img.src = e.target.result; }; reader.readAsDataURL(file); }); } function downloadPDF(dataUri) { var currentDate = new Date(); var year = currentDate.getFullYear(); var month = ('0' + (currentDate.getMonth() + 1)).slice(-2); var day = ('0' + currentDate.getDate()).slice(-2); var alphabet = generateRandomAlphabet(4); var hours = ('0' + currentDate.getHours()).slice(-2); // Add leading zero if necessary var minutes = ('0' + currentDate.getMinutes()).slice(-2); // Add leading zero if necessary var seconds = ('0' + currentDate.getSeconds()).slice(-2); // Add leading zero if necessary var milliseconds = ('00' + currentDate.getMilliseconds()).slice(-3); // Add leading zeros if necessary const link = document.createElement('a'); link.href = dataUri; link.download = 'JPEGtoPDF_' + year + month + day + hours + minutes + seconds + milliseconds + '.pdf'; link.click(); } </script> </body> </html>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

エラーメッセージというよりは警告ではないですか?
フォーム要素に<label>が振られていなかったり、placeholderが指定されていため、ユーザーに優しくないよ?ってニュアンスですね。

また、動作させるとコンソール上に以下のエラーメッセージが表示されます。

Uncaught TypeError: Cannot set properties of null (setting 'value')

確認してみると以下の箇所で発生しており、HTMLの構造的に存在しない要素を操作しようとしているため発生しているようです。

javascript

1input.parentNode.parentNode.querySelector('input[type="number"]').value = dpi;

parentNodeを連結して要素にアクセスしようとするよりも、何らかの変数に予め代入しておくと良いのではないでしょうか。

自分が実装するなら

※dpi周りの処理がNaNになるようですので、完全な動作テストは済んでいません。
※画面上の処理の簡略化は確認済みです。

html

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>JPEG to PDFコンバータ</title> 7 <link href="style.css" rel="styesheet"> 8 </head> 9 <body> 10 <table id="imageTable"> 11 <thead> 12 <tr> 13 <th>ページ数</th> 14 <th>JPEGアップロード</th> 15 <th>削除</th> 16 </tr> 17 </thead> 18 <tbody></tbody> 19 </table> 20 <button id="add-row">列追加</button> 21 <button id="to-pdf">変換</button> 22 <form id="dpiForm"> 23 <label for="dpi">DPI:</label> 24 <input type="number" id="dpi" name="dpi" min="1" step="1" required> 25 <button type="button" id="auto-fill-dpi">DPI自動入力</button> 26 </form> 27 <div class="overlayL" id="overlayL"> 28 <div class="loaderL"> 29 <img src="loading.svg" alt="処理中..." width="52px"> 30 </div> 31 </div> 32 <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script> 33 <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.2/html2canvas.min.js"></script> 34 <script src="script.js"></script> 35 </body> 36</html>

css

1.overlayL { 2 position: fixed; 3 width: 100%; 4 height: 100%; 5 top: 0; 6 left: 0; 7 background-color: rgba(0, 0, 0, 0.5); 8 display: none; 9 justify-content: center; 10 align-items: center; 11} 12.loaderL { 13 display: flex; 14 justify-content: center; 15 align-items: center; 16}

javascript

1'use strict'; 2class FormRows { 3 // 内部で持っておきたいデータ 4 __rows = []; // FormRowの一覧 5 __table = null; // 操作する<table> 6 __handler = _ => {}; // コールバック 7 8 constructor () {} 9 10 // FormRowを追加 11 addRow () { 12 const row = new FormRow(this); 13 row.setIndex(this.__rows.length + 1); 14 this.__rows.push(row); 15 this.__table.appendChild(row.element); 16 } 17 18 // FormRowを取得 19 getRow (index) { 20 return this.__rows[index]; 21 } 22 23 // コールバックの登録 24 setOnImageSelected (handler) { 25 this.__handler = handler; 26 } 27 28 // 内部データ登録 29 bind (table) { 30 this.__table = table; 31 } 32 33 requireDelete (index) { 34 // 例外処理してfalseも返す(= requireの失敗) 35 this.__rows.splice(index, 1); 36 this.__rows.filter(row => row.index > index) 37 .forEach(row => row.setIndex(row.index - 1)); 38 return true; 39 } 40 41 // ファイルが選ばれたと教えられたら、コールバックを実行する 42 onImageSelected () { 43 const dpi = this.__dpi; 44 this.__handler(dpi); 45 } 46 47 // 最小DPI取得 48 updateDpi (dpi) { 49 this.__dpi = Math.min(this.__dpi, dpi); 50 } 51 52 // pdfに変換する 53 toPdf () { 54 const dpi = this.__dpi; 55 const canvasPromises = []; 56 for (let i = 0; i < this.__rows.length; i++) { 57 const input = this.__rows[i].picker; 58 // 以下略 59 } 60 // asyncしたら括弧が要ります 61 Promise.all(canvasPromises).then(async (canvases) => { 62 const pdf = new jsPDF({ 63 orientation: 'p', 64 unit: 'in', 65 format: [8.5, 11] // ここ:Letterサイズならこうするが、A4,B5,A5,フリーサイズにするにはどうすればいいのか 66 // →A4サイズの幅と高さ指定で良いのでは? 67 }); 68 // 以下略 69 const pdfDataUri = pdf.output('datauristring'); 70 this.downloadPdf(pdfDataUri); 71 }).catch(error => { 72 console.error('Error converting images to PDF:', error); 73 }).finally(_ => { 74 }); 75 } 76 77 // 持っているFormRowのdpiで小さいものを返す 78 adjustDpi () { 79 return this.__rows.map(row => row.dpi).sort((a, b) => a - b)[0]; 80 } 81 82 downloadPdf () { 83 // 以下略 84 } 85} 86class FormRow { 87 // 内部で持っておきたいデータ 88 __index; 89 __parent = null; 90 __tr = null; 91 __td1 = null; 92 __td2 = null; 93 __td3 = null; 94 __dpi = 0; 95 96 // 内部データを取得する 97 get dpi () { return this.__dpi; } 98 get element () { return this.__tr; } 99 get index () { return this.__index; } 100 get picker () { return this.__picker; } 101 102 // <table>に追加する<tr>の作成と内部データ登録 103 constructor (parent) { 104 const tr = document.createElement('tr'); 105 const td1 = document.createElement('td'); 106 tr.appendChild(td1); 107 const td2 = document.createElement('td'); 108 const picker = document.createElement('input'); 109 picker.accept = 'image/jpeg'; 110 picker.onchange = _ => this.onImageSelected(picker.files[0]); 111 picker.type = 'file'; 112 td2.appendChild(picker); 113 tr.appendChild(td2); 114 const td3 = document.createElement('td'); 115 const button = document.createElement('button'); 116 button.onclick = _ => this.onDeleteSelf(); 117 button.textContent = '列削除'; 118 td3.appendChild(button); 119 tr.appendChild(td3); 120 this.__td1 = td1; 121 this.__td2 = td2; 122 this.__td3 = td3; 123 this.__tr = tr; 124 this.__picker = picker; 125 this.__parent = parent; 126 } 127 128 // <button>のクリック時に実行され、自信を削除する 129 onDeleteSelf () { 130 if (this.__parent.requireDelete(this.__index)) this.__tr.remove(); 131 } 132 133 // <button>のクリック時に実行され、FormRows側に変更を教える 134 onImageSelected (file) { 135 const image = new Image(); 136 const reader = new FileReader(); 137 reader.onload = event => { 138 image.src = reader.result; 139 image.onload = _ => { 140 // ここ、NaNになっていますね 141 // これでdpiが取得出来るのでしょうか? 142 const dpi = (image.width / file.width) * 72; 143 this.__dpi = dpi; 144 this.__parent.updateDpi(dpi); 145 // FormRows側にファイルが選ばれたことを教える 146 this.__parent.onImageSelected(); 147 }; 148 }; 149 reader.readAsDataURL(file); 150 } 151 152 // ページ数のテキスト変更など 153 setIndex (index) { 154 this.__index = index; 155 this.__td1.textContent = index; 156 } 157} 158 159// FormRows/FormRowをクラス化してメインのコードの簡略化 160const rows = new FormRows(); 161const table = document.getElementById('imageTable'); 162rows.setOnImageSelected(dpi => { 163 document.getElementById('dpi').value = dpi; 164}); 165rows.bind(table); 166rows.addRow(); 167rows.addRow(); 168rows.addRow(); 169rows.addRow(); 170rows.addRow(); 171document.getElementById('add-row').onclick = _ => rows.addRow(); 172document.getElementById('to-pdf').onclick = _ => rows.toPdf(); 173document.getElementById('auto-fill-dpi').onclick = _ => document.getElementById('dpi').value = rows.adjustDpi();

投稿2024/03/28 12:21

編集2024/03/28 14:58
Refrain

総合スコア527

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kofun

2024/03/28 13:57

ありがとうございます。その部分は、 reader.onload = function(e) { image.src = e.target.result; image.onload = function() { const dpi = (image.width / file.width) * 72; // 1px = 1/72 inchと仮定 const dpiInput = parentRow.querySelector('input[type="number"]'); dpiInput.value = dpi; }; }; このように変えたのですが、他にも問題があり、解決できませんでした。実現したいことに書いたことや、アプリの概要に書いたことが全て今のコードに反映されているかです。よろしくお願いします。
Refrain

2024/03/28 14:55

あまり知見が無いのですが、それでdpiは取れるものでしょうか… `file.width`などあったかな…?と疑問があります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問