3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

無料のChatGPTでブックマークレットを作って、ブラウザで動くミニアプリを作ろう

3
Posted at

はじめに

「ちょっとした便利アプリを作りたいけど、HTML/CSS/JavaScriptを全部自力で書くのはしんどい」。そんなときに相性がいいのが、ChatGPTにコードを書かせて、ブックマークレットとして動かす方法です。

ChatGPTの無料枠は利用可能で、OpenAIはコードの生成・レビュー・編集・質問応答を主要ユースケースとして案内しています。つまり、コード知識がそこまで多くなくても、「こういう動きをしてほしい」と日本語で伝えながら形にしていくやり方が十分現実的です。

さらにブックマークレットは、javascript: で始まるコードをブラウザのブックマークとして保存して実行する仕組みです。アプリストア公開も不要、サーバー準備も不要で、ブラウザがある端末なら試しやすいのが強いです。Chromeはブックマークを複数デバイスで使える案内があり、iPhoneのSafariでもWebページのブックマーク登録ができます。


この記事で伝えたいこと

この記事のポイントはシンプルです。

1つ目は、ChatGPTにコードを書かせたり修正させたりすれば、コード知識が少なくてもアプリを作りやすいこと。
2つ目は、ブックマークレットにしてしまえば、PCでもスマホでも、OSが違っても、ブラウザ経由で動かしやすいこと。

「本格的なWebアプリ開発」ではなく、まずは自分専用の小さな便利ツールを作るなら、この組み合わせはかなり強いです。


ブックマークレットとは何か

ブックマークレットは、普通のURLの代わりに javascript: で始まるコードをブックマークに入れておき、クリック時にそのJavaScriptを実行する仕組みです。MDNでも、javascript: URL はナビゲーション先として使われ、ブラウザがそのコードを実行すると説明されています。

要するに、**「Webページを開くブックマーク」ではなく、「その場で処理を実行するブックマーク」**です。

これが便利なのは、次のような点です。

  • インストール不要
  • サーバー不要
  • その場で動く
  • 空ページの上にUIを出せる
  • ちょっとした自作ツールに向いている

なぜChatGPTと相性がいいのか

ブックマークレットは本来、JavaScriptを1本書く必要があります。とはいえ今は、ChatGPTに対して

  • こういうミニアプリを作りたい
  • スマホでも押しやすいUIにして
  • ボタンを追加して
  • バグを直して
  • ブックマークレット用に1行化して

と頼めば、かなりのところまで持っていけます。

OpenAI自身も、コードの生成・レビュー・編集・デバッグを主要用途として案内しており、フロントエンドのワンショット生成例にも触れています。なので、自分は「仕様を日本語で詰める人」になり、実装のたたきをChatGPTに出してもらう進め方がしやすいです。

ここが大事で、コード力ゼロで何も考えなくていい、という話ではありません。
でも実際には、「全部手書きする」から「欲しい動きを言語化して直しながら作る」へ難易度が下がるので、個人開発の入口としてかなり優秀です。


今回作るもの

今回は例として、空ページ(about:blank)で使える注文メモアプリをブックマークレットで作ります。

アプリに求める機能は以下の通りです。

  • 商品名と値段の入力欄を追加できる
  • 商品ごとに誰が何個頼むかを入力できる
  • 税無・税有(8%)・税有(10%)計算がボタン一つでできる
  • 注文の一覧表示ができる
  • 一覧をコピーできる
  • 空ページ(about:blank)で動作させる

まずはChatGPTにこう頼む

最初の指示は、細かすぎる実装ではなく要件を渡すのがおすすめです。

ブックマークレットで動く以下の機能を持ったアプリを作ってください。
要件:
- 商品名と値段の入力欄を追加できる
- 商品ごとに誰が何個頼むかを入力できる
- 税無・税有(8%)・税有(10%)計算がボタン一つでできる
- 注文の一覧表示ができる
- 一覧をコピーできる
- 空ページ(about:blank)で動作させる
- 最後に bookmarklet 用に javascript: から始まる1行コードも出してください

この時点で出てきたコードは、たいていそのままだと微妙に使いづらいです。なので次に、

以下の修正をしてください。
- 増減量の下限は0。
- グループの入れ替えを可能にする
- 任意のグループの削除を可能にする
- テキストエリアをもう一つ追加する
- テキストエリアの役割は、商品名と値段を入力する
- 各商品の値段と数から、合計金額を表示する
- 合計金額表示欄の横には、消費税切り替えボタンを配置し、「税無」「税有(8%)」「税有(10%」で合計金額を切り替えられるようにする
上部バーに合計金額を表示したほうが使いやすいです。
親グループの「合計数量」表示 も併せて欲しいです。
子グループの入れ替え機能も追加してください。
他のグループで追加済みの名前に関しては、他のグループで新規追加したときに入力候補として選べるようにしてください。

のように、使いながら修正依頼を重ねるのがコツです。

また、ChatGPTが作成したコードでエラーが出る場合もあるため、その時はエラーメッセージを伝えることで、修正させることもできます。


作成してみたサンプルコード

以下は、今回作成したコードをそのままブックマークURLに入れやすい形にしたものです。

javascript:(function(){if(window.customOrderTool)return;window.customOrderTool=true;if(document.body.innerHTML.trim()===""){document.write("<!DOCTYPE html><html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1.0'></head><body></body></html>");document.close()}const style=document.createElement("style");style.textContent="#order-toolbar{position:fixed;top:0;left:0;right:0;background:#444;color:white;padding:8px;z-index:99999;display:flex;align-items:center;gap:8px;font-family:sans-serif;font-size:16px;flex-wrap:wrap}#groups-container{margin-top:70px;padding:0 5px}#summary-box{margin:10px 5px;padding:8px;border:2px solid #888;border-radius:8px;background:#f0f0f0}#summary-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px}#summary-list{white-space:pre-line;font-size:15px}.group{border:2px solid #888;margin:10px 5px;padding:10px;background:#f9f9f9;border-radius:8px}.group-header{display:flex;gap:6px;align-items:center;flex-wrap:wrap}.child{border:1px solid #ccc;margin:5px 0;padding:6px;border-radius:6px;background:#fff;display:flex;gap:5px;align-items:center;flex-wrap:wrap}input{font-size:16px;padding:4px}button{font-size:15px;padding:3px 6px}@media(max-width:600px){.group-header{flex-direction:column;align-items:flex-start}.child{flex-direction:column;align-items:flex-start}}";document.head.appendChild(style);const toolbar=document.createElement("div");toolbar.id="order-toolbar";toolbar.innerHTML='<button id="add-group">グループ追加</button><span>合計金額: <span id="total-amount">0</span> 円</span><button id="toggle-tax">税無</button>';document.body.prepend(toolbar);const container=document.createElement("div");container.id="groups-container";toolbar.insertAdjacentElement("afterend",container);const summaryBox=document.createElement("div");summaryBox.id="summary-box";summaryBox.innerHTML='<div id="summary-header"><span>注文一覧</span><button id="copy-summary">コピー</button></div><div id="summary-list"></div>';container.insertAdjacentElement("afterend",summaryBox);let taxMode=0;let nameHistory=new Set;function calcTotals(){let total=0;let summaryMap={};let detailMap={};container.querySelectorAll(".group").forEach(group=>{let groupTotal=0,groupQty=0;const price=parseInt(group.querySelector(".item-price").value)||0;const gName=group.querySelector(".item-name").value||"商品未入力";group.querySelectorAll(".child").forEach(child=>{const qty=parseInt(child.querySelector(".child-qty").textContent);const cname=child.querySelector(".child-name").value||"名無し";groupTotal+=qty*price;groupQty+=qty;if(qty>0){summaryMap[gName]=(summaryMap[gName]||0)+qty;detailMap[cname]=detailMap[cname]||{};detailMap[cname][gName]=(detailMap[cname][gName]||0)+qty}});group.querySelector(".group-total-qty").textContent=groupQty;group.querySelector(".group-total-price").textContent=groupTotal;total+=groupTotal});if(taxMode===8)total=Math.floor(total*1.08);if(taxMode===10)total=Math.floor(total*1.10);document.getElementById("total-amount").textContent=total;let summaryText="--- 商品集計 ---\n"+Object.entries(summaryMap).map(([n,q])=>`${n} × ${q}`).join("\n");let detailText="--- 注文詳細 ---\n"+Object.entries(detailMap).map(([person,items])=>{let list=Object.entries(items).map(([p,q])=>`${p} × ${q}`).join(", ");return `${person}: ${list}`}).join("\n");document.getElementById("summary-list").textContent=summaryText+"\n\n"+detailText;return{total,summaryText,detailText}}function createGroup(){const group=document.createElement("div");group.className="group";group.innerHTML='<div class="group-header"><label>商品名: <input type="text" class="item-name" placeholder="商品名"></label><label>値段: <input type="number" class="item-price" value="0" min="0">円</label><div>合計数量: <span class="group-total-qty">0</span></div><div>小計: <span class="group-total-price">0</span>円</div><div><button class="add-child">子追加</button><button class="delete-group">削除</button></div></div><div class="children"></div>';group.querySelector(".add-child").addEventListener("click",()=>{const childrenContainer=group.querySelector(".children");const child=document.createElement("div");child.className="child";child.innerHTML='<label>名前: <input type="text" class="child-name" list="name-suggestions"></label><div>数量: <span class="child-qty">0</span><button class="child-plus">+</button><button class="child-minus">-</button><button class="child-up">↑</button><button class="child-down">↓</button><button class="delete-child">削除</button></div>';const nameInput=child.querySelector(".child-name");nameInput.addEventListener("change",()=>{if(nameInput.value){nameHistory.add(nameInput.value);updateDatalist()}});child.querySelector(".child-plus").addEventListener("click",()=>{let qty=parseInt(child.querySelector(".child-qty").textContent);child.querySelector(".child-qty").textContent=qty+1;calcTotals()});child.querySelector(".child-minus").addEventListener("click",()=>{let qty=parseInt(child.querySelector(".child-qty").textContent);if(qty>0){child.querySelector(".child-qty").textContent=qty-1;calcTotals()}});child.querySelector(".delete-child").addEventListener("click",()=>{child.remove();calcTotals()});child.querySelector(".child-up").addEventListener("click",()=>{const prev=child.previousElementSibling;if(prev){childrenContainer.insertBefore(child,prev)}});child.querySelector(".child-down").addEventListener("click",()=>{const next=child.nextElementSibling;if(next){childrenContainer.insertBefore(next,child)}});childrenContainer.appendChild(child)});group.querySelector(".delete-group").addEventListener("click",()=>{group.remove();calcTotals()});group.querySelector(".item-price").addEventListener("input",calcTotals);group.querySelector(".item-name").addEventListener("input",calcTotals);container.appendChild(group)}const datalist=document.createElement("datalist");datalist.id="name-suggestions";document.body.appendChild(datalist);function updateDatalist(){datalist.innerHTML="";Array.from(nameHistory).forEach(name=>{const opt=document.createElement("option");opt.value=name;datalist.appendChild(opt)})}document.getElementById("add-group").addEventListener("click",createGroup);document.getElementById("toggle-tax").addEventListener("click",e=>{if(taxMode===0){taxMode=8;e.target.textContent="税有(8%)"}else if(taxMode===8){taxMode=10;e.target.textContent="税有(10%)"}else{taxMode=0;e.target.textContent="税無"}calcTotals()});document.getElementById("copy-summary").addEventListener("click",()=>{const{total,summaryText,detailText}=calcTotals();const text=`合計金額: ${total} 円\n${summaryText}\n\n${detailText}`;const ta=document.createElement("textarea");ta.value=text;document.body.appendChild(ta);ta.select();try{document.execCommand("copy")}catch(e){}ta.remove()})})();

使い方

1. ブックマークにコードを登録する
ブックマークのURL欄に、作成したコードを記述します。
※画像はMicrosoft Edgeのブックマーク登録例
img0.jpg

PCで使う

Chromeでは、Webページを開いてアドレスバー右側からブックマーク追加できます。保存後、そのブックマークのURLを上の javascript:... に置き換えればOKです。Chromeのヘルプでも、ブックマーク追加やブックマークバー表示方法が案内されています。 Google Chrome Help

スマホで使う

iPhoneのSafariでもWebページをブックマークできます。Appleのサポートでは、Safariでページを開いてブックマーク追加する手順が案内されています。 Apple Support

AndroidのChrome系環境でもブックマークは扱えますし、GoogleはChromeのブックマークを同一アカウントで複数デバイスにまたがって利用できると案内しています。つまり、一度作ったブックマークレットをPCとスマホで持ち回りしやすいです。 Google Chrome Help

2. ブラウザの空ページを開く
ブラウザのURL欄に「about:blank」と入力して、空のページを開く
img1.jpg

3. 登録したブックマークレットを実行する
登録したブックマークをクリックすると、アプリが実行される
img3.jpg

【ブックマークレットを実行したブラウザ画面】
img2.jpg


ここが良い: コード知識が少なくても前に進める

この方法のいちばん大きい価値は、「実装能力」より「要件を言葉にする力」で前進しやすいことです。

たとえば普通の開発だと、

  • DOM操作を書く
  • CSSを書く
  • localStorageを扱う
  • イベントを書く
  • エラーを潰す
  • 圧縮してブックマークレット化する

と、意外とやることが多いです。

でもChatGPTに対して
「テキストエリアをもっと大きくして」
「閉じるボタンを右上にして」
「二重起動を防いで」
「保存完了を表示して」
と日本語で修正依頼を重ねれば、かなり実用レベルまで持っていけます。

もちろん、出力されたコードをそのまま盲信するのは危険です。
ただ、ゼロから全部書くのに比べると圧倒的に楽です。


ここが良い: ブックマークレットだから配布も実行も軽い

ブックマークレットの良さは、「ブラウザで動く小さな道具」として扱えることです。

通常のアプリ開発だと、環境構築・ホスティング・公開・アップデートなどが発生します。
一方でブックマークレットなら、基本的にはコードを1本作ってブックマークに入れるだけです。

しかも、ブラウザ上で動くのでOS依存をかなり減らせます。WindowsでもMacでも、iPhoneでもAndroidでも、ブラウザとブックマーク機能が使える環境なら試しやすいのが魅力です。Chromeはブックマークを各デバイスで利用でき、SafariでもiPhoneでブックマーク登録が可能です。


ただし注意点もある

万能ではありません。

まず、javascript: URL はブラウザやページ側の制約を受けます。MDNでも、Content Security Policy(CSP)によって javascript: ナビゲーションがブロックされる場合があると説明されています。つまり、サイトによっては動かないことがあります。

また、ブックマークレットは基本的に今開いているページの上で動くものです。なので、大規模なアプリというよりは、

  • 文章整形
  • ページ補助
  • ちょいメモ
  • 入力補助
  • 表示切り替え
  • 社内業務の半自動化

のような、小回りの利く道具に向いています。


ChatGPTに追加で頼むと便利な修正例

1度の指示で期待通りのアプリを作らせるのは難しいので、同じコードを少しずつ育てるフェーズが重要です。たとえばこんな依頼で少しずつ求めているアプリに近づけていくことができます。

期待している挙動ではないことを伝える

コピー機能が、PCでは機能するが、iPhoneでは機能しなかった

使用感を伝えてみる

上部バーに合計金額を表示したほうが使いやすい。

エラーを伝える

以下のエラーが出る
※エラーメッセージをコピペ

要望を追加する

完全に空のページ(about:blank)で使用したい
最後にブックマークレット用の1行コードも必ず出してください

この進め方をすると、最初は簡単なアプリでも、会話しながら少しずつ育てられるのがかなり楽しいです。


まとめ

無料のChatGPTを使ってブックマークレットを作る方法は、
コード知識が少なくても、自分の欲しい小さなアプリを形にしやすいのが魅力です。

そしてブックマークレットにしてしまえば、
ブラウザがある端末なら、PCでもスマホでも、OSが違っても試しやすいです。Chromeはブックマークをデバイス間で利用でき、SafariでもiPhoneでブックマークが使えます。

「本格的な開発はまだ重いけど、まずは何か1つ作りたい」という人ほど、このやり方はハマると思います。
最初の1本は、ChatGPTに相談しながら、自分専用の小さな便利ツールを作ってみるのがおすすめです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?