写真を選んで記念日を設定!写真と記念日を記したカレンダーを表示するアプリ

@ハクト 2024-11-13 15:28:18に投稿

JavaScriptでCanvasを使用し、選択した写真と記念日を記したカレンダーを表示するアプリをChatGPTに指示をして作りました。

記念日を記したカレンダー写真

使用した言語など

今回はHTMLのペラページに以下のような言語やツールを使用して作成しました。

言語

  • HTML

  • CSS

  • JavaScript

ツール・フォント・ライブラリ

  • ChatGPT

  • Flatpickr(日付選択ライブラリ)

  • Copse(Googleフォント)

使い方

1.画像を選択

画面上部の「画像を選択」ボタンをクリックし、背景にしたい写真をアップロード。選んだ画像が自動的にCanvasの左側に表示されます。

2.日付を選択

「日付を選択」ボタンをクリックし、記念日として表示したい日付をカレンダーから選択。選択した日付がカレンダーに反映され、記念日として強調表示されます。

3.ダウンロード

画面下部の「ダウンロード」アイコンが表示されたボタンをクリックすると、Canvasに表示された内容が画像ファイルとして保存されます。

 

機能別ChatGPTへの指示内容

以下、各機能別に指示内容のサンプルをまとめました。ChatGPTを活用して生成されたコードをベースにしていますが、指示内容はさらに細かく、生成されたコードも全てそのまま使用したわけではありません。

生成されたコードについて再度ChatGPTに解説してもらい、必要に応じて手動で修正や調整を加えています。

画像選択機能

機能

  • 画像選択ボタンを設置し、ユーザーが任意の写真を背景に設定できるようにする。

主な指示内容

  • 画像選択ボタンを押すと、画像ファイルを選択可能にする。

  • 選択された画像がCanvasに読み込まれるようにする。

日付選択機能

機能

  • 日付選択ボタンを設置し、Flatpickrライブラリを用いて、記念日の日付をユーザーが選択できるようにする。

主な指示内容

  • Flatpickrを用いて、選択された日付をCanvas上にカレンダーとして反映する。

  • カレンダーは日付選択ボタンの下に表示する。

  • 日付変更時にはカレンダーが自動更新され、選択した日付をボタンに表示にする。

Canvas設定(背景画像とカレンダー表示)

背景画像の表示

機能
 

  • Canvasの左側に背景画像を表示し、約55%の幅を占めるようにする。

主な指示内容

  • ユーザーが選択した画像をCanvasにトリミングして表示する。

  • アスペクト比を保ち、CSSのobject-fit: cover;のように画像の中心が美しく収まるようにする。

  • トリミングされた画像をCanvasの左側に約55%の幅で表示。

カレンダー表示と記念日

機能

  • Canvasの右側にカレンダーを表示し、Canvasの約45%を使用する。

主な指示内容

  • カレンダーにはGoogleフォントのCopseを使用し、卓上カレンダーのように上から順に年と月、曜日、日付を表示。

  • 数字の月の下には英語の月も表示して、年と月は中央に配置され、視覚的に目立つよう大きなフォントサイズを設定。

  • 曜日ごとに色分け(日曜は赤、土曜は青、平日は黒)し「Sun、Mon、Tue」のように出力。

  • 選択された日付が角丸枠で強調され、赤色の枠で塗り潰し、日付が白色で強調表示されるように設定。

  • 曜日や日付のサイズ・間隔を計算し、画面幅が変更された場合にもレイアウトが崩れないように調整。

  • カレンダーの左右に30pxの余白を設定

デザインとレイアウト

機能

  • 全体の見た目と操作性を向上させるため、UIのデザインとレイアウトを整える。

主な指示内容

  • Canvas、画像選択ボタン、日付選択ラベルが画面中央に配置されるように設定。

  • ボタンにはホバー時の色変化や角の丸みを加えて、操作性を向上。

ダウンロード機能

機能

  • Canvasに表示されたカレンダーを画像ファイルとしてダウンロードできるようにする。

主な指示内容

  • ダウンロードボタンを押すと、Canvas内容を画像として保存されるように設定。

機能別ソースコード解説

1. 画像選択機能


// JavaScriptコード
document.getElementById('image-input').addEventListener('change', function (event) {
  const file = event.target.files[0];
  const reader = new FileReader();
  reader.onload = function (e) {
    const img = new Image();
    img.crossOrigin = 'anonymous'; // クロスオリジン設定
    img.onload = function () {
      selectedImage = img;
      drawCanvas(); // 画像が読み込まれたら描画
    };
    img.src = e.target.result;
  };
  reader.readAsDataURL(file);
});

概要

  • ユーザーが画像選択ボタンで画像を選ぶと、その画像をCanvas上に表示する機能

解説

  • FileReaderを使用して、ユーザーが選択した画像ファイルを読み込む

  • 画像が読み込まれた後、selectedImage変数に格納し、drawCanvas()関数を呼び出してCanvas上に描画

  • crossOriginプロパティを設定することで、画像のクロスオリジン制限を回避

2. 日付選択機能


// JavaScriptコード
flatpickr("#date-input", {
  defaultDate: new Date(),
  onChange: function(selectedDates, dateStr, instance) {
    document.getElementById('date-text').textContent = dateStr || '日付を選択';
    drawCanvas(); // 日付が変更されたときに再描画
  },
  appendTo: document.body,
  positionElement: document.getElementById('date-label')
});

概要

  • Flatpickrライブラリを使用して、ユーザーが記念日の日付を選択できるようにし、Canvasに反映する機能

解説

  • flatpickrを使用して、日付選択用のカレンダーUIを提供

  • onChangeイベントで日付が変更されるたびにdrawCanvas()関数を呼び出し、Canvasを更新

  • 選択した日付はdate-text要素に表示され、未選択の場合は「日付を選択」と表示

3. Canvas設定(背景画像とカレンダー表示)

背景画像の表示


// JavaScriptコード
function cropImageToFitCanvas(image, targetWidth, targetHeight) {
  const tempCanvas = document.createElement('canvas');
  const tempCtx = tempCanvas.getContext('2d');
  tempCanvas.width = targetWidth;
  tempCanvas.height = targetHeight;

  const imageAspectRatio = image.width / image.height;
  const canvasAspectRatio = targetWidth / targetHeight;

  let drawWidth, drawHeight, offsetX = 0, offsetY = 0;

  // アスペクト比を維持しつつ、画像をトリミングして仮のCanvasに描画
  if (imageAspectRatio > canvasAspectRatio) {
    drawHeight = targetHeight;
    drawWidth = drawHeight * imageAspectRatio;
    offsetX = (drawWidth - targetWidth) / 2;
  } else {
    drawWidth = targetWidth;
    drawHeight = drawWidth / imageAspectRatio;
    offsetY = (drawHeight - targetHeight) / 2;
  }

  // 仮のCanvasに画像をトリミングして描画
  tempCtx.drawImage(image, -offsetX, -offsetY, drawWidth, drawHeight);
  return tempCanvas;
}

概要

  • 画像のアスペクト比を維持したまま、Canvasの左側に適切に収まるようトリミングする機能

解説

  • 一時的なtempCanvasを作成し、画像をトリミング

  • 画像とCanvasのアスペクト比を比較し、画像のサイズと位置を計算

  • トリミングした画像をdrawImage()で描画し、結果を返す

カレンダー表示と記念日


function drawCanvas() {
	const imgWidth = canvas.width * 0.55;
	const imgHeight = canvas.height;

	ctx.clearRect(0, 0, canvas.width, canvas.height);  // カレンダー用Canvasをクリア

	// 背景を白で塗りつぶす
	ctx.fillStyle = '#fdfefd';
	ctx.fillRect(0, 0, canvas.width, canvas.height);

	// トリミングした画像を描画
	if (selectedImage) {
		const croppedImage = cropImageToFitCanvas(selectedImage, imgWidth, imgHeight);
		ctx.drawImage(croppedImage, 0, 0, imgWidth, imgHeight);  // 左側にトリミングされた画像を描画
	}

	// カレンダー表示に必要な年月と選択された日付
	const selectedDate = new Date(document.getElementById('date-input').value);
	const year = selectedDate.getFullYear();
	const month = selectedDate.getMonth();
	const day = selectedDate.getDate();
	const daysInMonth = new Date(year, month + 1, 0).getDate();
	const firstDayOfMonth = new Date(year, month, 1).getDay(); // 月の初日の曜日
	const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

	// カレンダーの配置位置(右エリアはCanvasの45%)
	const calendarWidth = canvas.width * 0.45 - 60; // 左右に30pxの余白を設定
	const calendarX = canvas.width * 0.55 + 30; // 左右30px余白を考慮
	const calendarY = (canvas.height / 2) / 2; // カレンダー全体の高さを280pxとして上下中央に配置
	const daySize = (calendarWidth - 6 * 15) / 7; // カレンダーの幅から計算(6つの余白を考慮)
	const weekdayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

	// 月と年をカレンダー中央に表示
	ctx.textAlign = 'center';
	ctx.fillStyle = 'black';


	const calendarCenterX = calendarX + calendarWidth / 2;
	const fontFamily = 'Copse';

	// 年(小さく)
	ctx.font = `32px ${fontFamily}`;
	ctx.fillText(`${year}`, calendarCenterX, calendarY - 105);

	// 月(大きく)+ 英語の月(小さく)
	ctx.font = `64px ${fontFamily}`;
	ctx.fillText(`${month + 1}`, calendarCenterX, calendarY - 35);
	ctx.font = `28px ${fontFamily}`;
	ctx.fillText(`${monthNames[month]}`, calendarCenterX, calendarY);

	// 曜日ラベルを描画
	ctx.font = `24px ${fontFamily}`;  // 曜日のサイズをさらに小さく
	for (let i = 0; i < 7; i++) {
		const x = calendarX + i * (daySize + 15);  // 曜日の余白調整
		const y = calendarY + 55;

		if (i === 0) {
			ctx.fillStyle = 'rgba(255, 0, 0, 0.7)'; // 日曜日は赤
		} else if (i === 6) {
			ctx.fillStyle = 'rgba(0, 0, 255, 0.7)'; // 土曜日は青
		} else {
			ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; // 平日は黒
		}
		ctx.textAlign = 'center'; // 曜日を中央に配置
		ctx.fillText(weekdayNames[i], x + daySize / 2, y);
	}

	// カレンダーの日付を描画
	let dayCounter = 1;
	for (let i = 0; i < 6; i++) {  // 最大6行
		for (let j = 0; j < 7; j++) {  // 7列 (曜日)
			const x = calendarX + j * (daySize + 15);
			const y = calendarY + 65 + i * (daySize + 15); // 日付の配置を少し下げる

			if (i === 0 && j < firstDayOfMonth) {
				// 1行目で月の開始前の日付は空欄に
				continue;
			}

			if (dayCounter > daysInMonth) {
				// 月末を超えたら終了
				break;
			}

			// 日付の描画(中央寄せ)
			ctx.textAlign = 'center';
			if (j === 0) {
				ctx.fillStyle = 'rgba(255, 0, 0, 0.7)'; // 日曜日は赤
			} else if (j === 6) {
				ctx.fillStyle = 'rgba(0, 0, 255, 0.7)'; // 土曜日は青
			} else {
				ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; // 平日は黒
			}

			// 選択された日付をピンクの角丸四角で囲む
			if (dayCounter === day) {
				drawRoundedRect(ctx, x, y - 8, daySize, 50, 10);
				ctx.fillStyle = '#c43215';
				ctx.fill();
				ctx.strokeStyle = '#c43215';
				ctx.lineWidth = 1;
				ctx.stroke();

				// カレンダーの数字カラーを白に
				ctx.fillStyle = 'white'; // 白色に変更
			}
			ctx.fillText(dayCounter, x + daySize / 2, y + 25);  // 日付を中央に配置

			dayCounter++;
		}
	}
}

概要

  • Canvasの右側にカレンダーを表示し、選択した記念日を強調表示する機能

解説

  • drawCanvas()関数内で背景画像とカレンダーを描画

  • カレンダーは雑誌の表紙デザインのように、情報が上から順に整理された構成

  • 年と月を大きなフォントで中央上部に表示し、視覚的に目立たせる

  • 曜日はその下に横一列で配置し、曜日ごとに色分けして視認性を向上

  • 日付は複数行にわたり表示し、選択された記念日は特別な色と枠で強調表示

4. デザインとレイアウト


/* CSSコード */
.canvas {
  width: 800px;
  height: 500px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.custom-label {
  width: 80px;
  height: 50px;
  border: 1px solid #0ca0cd;
  border-radius: 5px;
  text-align: center;
  cursor: pointer;
  background-color: #f0f0f0;
}

.custom-label:hover {
  background-color: #0ca0cd;
}

.download-button {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  background-color: #efefef;
  border-radius: 5px;
  cursor: pointer;
  text-decoration: none;
}

概要

  • Canvasや各種ボタンのデザインを整え、全体のレイアウトを調整する機能

解説

  • .canvasクラスでCanvasのサイズや枠線、角の丸みを設定

  • .custom-labelクラスで画像選択や日付選択ボタンのスタイルを設定

  • .download-buttonクラスでダウンロードボタンのデザインを設定

5. ダウンロード機能


// JavaScriptコード
document.getElementById('downloadButton').addEventListener('click', function() {
  canvas.toBlob(function(blob) {
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'calendar.png';
    link.click();
    URL.revokeObjectURL(link.href);
  }, 'image/png');
});

概要

  • Canvasに表示されたカレンダーを画像としてダウンロードできるようにする機能

解説

  • canvas.toBlob()を使用して、Canvasの内容をPNG形式のBlobに変換

  • 一時的なリンクを作成し、そのリンクをクリックすることで画像をダウンロード

  • ダウンロード後、URL.revokeObjectURL()で一時的なURLを解放

まとめ

以上がChatGPTへの機能別指示内容と機能ごとのコード内容となります。

ざっくりとした機能はChatGPTが生成するコードだけで実装できましたが、余白や画像のトリミング、ボタンのデザインなどは手動で調整した部分もあります。

何か参考になれば幸いです。

@ハクト

サービス作り・デザイン好き。70年代生まれのWEBエンジニア。WEBパーツをCSSでカスタマイズしてコピペできるサービスを運営中「Pa-tu」。実装したWEBパーツやツールを利用してWEB情報やライフハックを発信してます。

Twitter