l

o

a

d

i

n

g

.

.

.

GoogleFormで画像送信はできるのか?

2026/07/05

HTML Javascript Web制作

t f B! P L
eyecatch 先日、「ホームページの問い合わせで画像も送ってもらえるようにしたい」と言われ、どのようにすればいいかを検討してみた。 PHPが使えるサーバーを使っているのであれば、formで送信してサーバーで受け取ればいいのですが、 個人的には、やっぱりお手軽なGoogleFormを使って行いたいという欲求がある。 さらに、小規模サイトの場合は、わざわざサーバーセットなどしなくても、Googleの無料枠でスプシ管理からデータ(画像)管理までできてしまったら、非常に便利だと思いません? そして、いろいろ調べて、紆余曲折ありながら、小規模webサイトで使える画像も送信できるGoogleFormの拡張方法を見出せたので、 ブログに書き残しておきたいと思います。

GoogleFormの画像アップロード機能

一昔前は、文字列しか送信できなかった、Google Formも、今では、ファイルアップロード機能がついています。
【注意事項】でも、この設定を使っては行けません。 なぜなら、この機能をセットしてしまうと、WebサイトにGoogleFormを埋め込めなくなってしまうからです。 GoogleFormの発行URLでアクセスしたフォームまたは、iframeを使って埋め込む方法でよければ、これを使えば問題ありません。 しかし、ホームページのデザインやUIなどを重視したい場合は、GoogleFormのデフォルトデザインをそのまま使うというのは、かなり厳しいでしょう。 ちなみに、デフォルトのGoogleFormでファイルアップロードをしたら、GoogleDriveにファイルが保存される仕様で、スプレッドシートには、GoogleDriveのURLが追加される仕様です。 ということで、GoogleFormだけでのファイルアップロード機能はできないという結論に至りました。

GoogleDrive + GAS

そこで、Googleには、超絶便利なGoogleAppsScriptがあります。 これを使って、GoogleDriveにファイル(画像など)をアップロードできるようにすれば、問題なくね?という思考に行きつきました。 そして、GoogleFormには、GoogleDriveのURLを登録すれば、GoogleFormのアップロードと同じ結果にすることができます。 と言うことで、その手順の覚書です。

GoogleAppsScriptで新規プロジェクトを作る。

環境構築をするGoogleアカウントでログインした状態で、次のURLにアクセスすると、GAS (GoogleAppsScript)の新規プロジェクト作成になります。 https://script.google.com/home/projects/create

GASプログラム

表示されているエディタに以下のソースを貼り付けます。 const FOLDER_ID = " %GoogleDriveにフォルダを作ってURLのID部分を貼り付ける "; function doPost(e) { try { const folder = DriveApp.getFolderById(FOLDER_ID); const data = JSON.parse(e.postData.contents); // data:image/jpeg;base64,... const matches = data.image.match(/^data:(.+);base64,(.+)$/); if (!matches) { throw new Error("画像形式が不正です"); } const mimeType = matches[1]; const bytes = Utilities.base64Decode(matches[2]); const ext = mimeType.split("/")[1]; const filename = Utilities.formatDate( new Date(), Session.getScriptTimeZone(), "yyyyMMdd_HHmmss" ) + "." + ext; const blob = Utilities.newBlob(bytes, mimeType, filename); const file = folder.createFile(blob); // リンクを知っている全員 file.setSharing( DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW ); const fileId = file.getId(); const imageUrl = "https://drive.google.com/uc?export=view&id=" + fileId; return ContentService .createTextOutput(JSON.stringify({ success: true, fileId: fileId, url: imageUrl })) .setMimeType(ContentService.MimeType.JSON); } catch(err){ return ContentService .createTextOutput(JSON.stringify({ success:false, message:err.toString() })) .setMimeType(ContentService.MimeType.JSON); } }

デプロイセット

1. 「デプロイ」→「新しいデプロイ」 2. 種類 : ウェブアプリ 3. 実行者 : 自分 4. アクセスできるユーザー : 全員 上記をセットした後、ウェブアプリのURLを取得。 ※こんなヤーツ https://script.google.com/macros/s/XXXXXXXX/exec これをこの後セットするホームページ側のJavascriptにコピペします。

ホームページの設定

実際に、サンプル構築したソースコードを掲載しておく。 ※ハッシュやIDは、伏せています。

index.html

<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <link href="css/style.css" rel="stylesheet" type="text/css"> <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0" name="viewport"> <link href="favicon.ico" rel="icon" type="image/x-icon"> <link href="favicon.ico" rel="apple-touch-icon" type="image/x-icon"> <link href="https://example.com/" rel="canonical"> <title>タイトル</title> <meta content="ホームページ説明" name="description"> <script type="module" src="js/main.js"></script> <!-- OGP --> <meta property="og:title" content="ページタイトル" /> <meta property="og:type" content="website" /> <meta property="og:url" content="https://example.com/" /> <meta property="og:image" content="https://example.com/" /> <meta property="og:site_name" content="ホームページタイトル" /> <meta property="og:description" content="ホームページ説明" /> <meta property="og:locale" content="ja_JP" /> <meta property="fb:app_id" content="" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:description" content="" /> <meta name="twitter:site" content="@アカウント名" /> <meta name="twitter:image" content="https://example.com" /> <meta property="image_src" content="https://example.com" /> </head> <body> <form id="form_1" method='post'> <input type="hidden" name="entry.xxxxx" data-name="FILE_ID"> <input type="hidden" name="entry.xxxxx" data-name="FILE_URL"> <p>何に関するお問い合わせですか?</p> <input type="text" name="entry.xxxxx" required> <p>名前</p> <input type="text" name="entry.xxxxx" required> <p>メールアドレス</p> <input type="text" name="entry.xxxxx" required> <p>お問い合わせ内容</p> <input type="text" name="entry.xxxxx" required> <p>写真</p> <label for="photo">写真を選択</label> <button type="submit" name="submit">送信</button> </form> <input id="photo" type="file" accept="image/jpeg,image/png,image/webp" required/> <iframe id="complete" name="complete" style="display:none;"></iframe> </body> </html>

css/style.css

form{ display:flex; flex-direction:column; gap:0.5em; } form input[type="text"]{ border:1px solid black; border-radius:0.3em; padding:0.3em 0.5em; width:300px; } form button[name="submit"]{ width:300px; border-radius:0.3em; border:1px solid blue; background-color:blue; color:white; padding:1.0em; cursor:pointer; margin-top:2.0em; } form button[name="submit"]:hover{ opacity:0.7; } form p{ margin-bottom:0.1em; } form label[for="photo"]{ width:100px; border:1px solid black; border-radius:0.5em; padding:0.5em; text-align:center; cursor:pointer; } form label[for="photo"]:hover{ background-color:#FEE; } #photo{ display:none; }

js/main.css

import { GoogleForm } from "./google_form.js" import { GoogleDrive } from "./google_drive.js" class Main{ constructor(){ this.init() } init(){ new GoogleForm().init({ form_selector : "#form_1", action : 'https://docs.google.com/forms/d/xxxxxx/formResponse', iframe_selector : '#complete', redirect : 'thanks.html', }) new GoogleDrive().init({ input_type_selector : "#photo", value_selector : "label[for='photo']", input_file_id_selector : "input[data-name='FILE_ID']", input_file_url_selector : "input[data-name='FILE_URL']", }) } } switch(document.readyState) { case "complete": case "interactive": new Main();break default: window.addEventListener("DOMContentLoaded", () => {new Main()}) }

js/google_form.js

export class GoogleForm { init(option){ this.option = this.validate(option) if(!this.option){return} option.form.target = option.iframe.id option.form.action = option.action option.form.addEventListener("submit", this.submit.bind(this)) if(option.file_upload){ this.file_upload_init() } } validate(option){ if(!option.form_selector){return} option.form = document.querySelector(option.form_selector) || null if(!option.form){return} if(!option.action){return} option.iframe = document.querySelector(option.iframe_selector) || null if(!option.iframe){return} if(option.file_upload_selector){ option.file_upload = document.querySelector(option.file_upload_selector) || null } return option } submit(e){ // submit処理 this.option.submit && this.option.submit() // redirect if(this.option.redirect){ location.href = this.option.redirect } } }

js/google_drive.js

export class GoogleDrive { GAS_URL = "https://script.google.com/macros/s/xxxxxxxx/exec" init(options){ this.options = this.validate(options) if(!this.options){return} this.options.input_type.addEventListener("change", this.upload.bind(this)) } validate(options){ if(!options || !options.input_type_selector){return} options.input_type = document.querySelector(options.input_type_selector) || null if(!options.input_type){return} if(options.input_file_id_selector){ options.input_file_id = document.querySelector(options.input_file_id_selector) || null } if(options.input_file_url_selector){ options.input_file_url = document.querySelector(options.input_file_url_selector) || null } if(options.value_selector){ options.value_elm = document.querySelector(options.value_selector) || null } return options } upload(e){ e.preventDefault(); const file = document.querySelector("#photo").files[0]; if(!file){ alert("画像を選択してください"); return; } const reader = new FileReader(); reader.onload = async ()=>{ this.loading_on() try{ const response = await fetch(this.GAS_URL,{ method:"POST", body:JSON.stringify({ image:reader.result }) }); const json = await response.json(); console.log(json); if(!json.success){ alert(json.message); return; } // ファイル名表示 if(this.options.value_elm){ this.options.value_elm.innerText = file.name } // ここでGoogleFormへPOST(取得したアップ後の画像情報の登録) this.set_input_value(json) this.loading_off() } catch(err){ this.loading_off() console.error(err); } }; reader.readAsDataURL(file); } set_input_value(data){ if(this.options.input_file_id){ this.options.input_file_id.value = data.fileId } if(this.options.input_file_url){ this.options.input_file_url.value = data.url } } loading_on(){ const div = document.createElement("div") div.id = "loading" div.style.position = "fixed" div.style.top = 0 div.style.left = 0 div.style.width = "100%" div.style.height = "100dvh" div.style.display = "flex" div.style.alignItems = "center" div.style.justifyContent = "center" div.style.backgroundColor = "rgba(0,0,0,0.5)" div.style.zIndex = 9999 div.innerHTML = "<p style='color:white;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'>アップロード中...</p>" document.body.appendChild(div) } loading_off(){ const loading = document.getElementById("loading") if(!loading){return} loading.parentNode.removeChild(loading) } }

GoogleSpreadSheet

上記のコードで送信をしてみたところ、正常に画像のURLがちゃんとくっついてきていた。 ※GoogleFormに、FILE_IDとFILE_URLを追加するのがミソ。(FILE_IDは、念の為の保険)
これに一手間加えて、 画像プレビュー用の列を追加して、以下をセットするだけで、自動的に画像表示までされるようになります。 =IMAGE(セル座標)

あとがき

今回は画像でやりましたが、GoogleDriveにアップできるファイルであれば、なんでも自分のホームページから送信できるようになります。 動画などの容量が大きいファイルは容量オーバーで送信できませんが、簡単なデータのやり取りや、今回のように画像をファイルアップしたり、お問い合わせの添付画像(資料)などが必要なフォームで同じように利用することができるようになります。 これまで、文字ベースだけのお問い合わせで、GoogleFormを使っていたんですが、ファイルアップロードによって、より幅のある使い方ができることがわかりました。 これを使った、仕事でのホームページ制作も問題ナシですね。

参考記事

これは便利、CGIを一切使わずにformデータが収集できる、Google form活用方法

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ