l

o

a

d

i

n

g

.

.

.

AIプログラミング学習 Kiroを覚えて爆速プログラミング #02 既存プロジェクトの改修について

2026/04/07

AI テクノロジー 学習

t f B! P L
eyecatch Kiroを使うと、開発部門のマネージャーになったような感覚になる。 適切にプロジェクトの内容を指示して、適切に要件定義を作らせて、実際のコードを書いてもらう。 そして、それらの工程をチェックする。 これって、上流工程作業じゃ無いですか! 新しいプロジェクト開発もいいですが、古いシステムの改善を行ってもらうのもAIの得意分野だと思うので、 今回は以前に作ってgithubにアップしている、カルーセルのライブラリの改善提案をしてもらって、 README.mdを書き直してもらって、その提案通りに修正を行ってもらい、テストまで実装してもらおうと思います。

改善してもらうプロジェクト

以前にブログでも書いた、少し見栄えのいいオート式カルーセルのCSSライブラリです。 以前のブログリンク : [CSS] フォーカス・カルーセル Github : focus_carousel

改善提案をしてもらう

  • このプロジェクトの問題点を見つけて改善提案をして。
res : 1m14s

主な問題点

1. セキュリティとパフォーマンス HTMLに<head>タグがなく、<html>、<body>タグも欠落 メタタグやスタイルシートが適切な構造内に配置されていない cryptoの存在チェックが不適切(uuid.js) 2. コードの品質 carousel.jsでPromiseを使用しているが、実際には非同期処理がない scroll_endイベントは比較的新しいAPIで、ブラウザ互換性に問題がある可能性 エラーハンドリングが不足 ~~演算子の使用(可読性が低い) 3. アクセシビリティ キーボード操作のサポートがない ARIA属性が不足 画像に適切なalt属性がない 4. 機能面 READMEに記載されている「少ない個数の場合」の対応が未実装 無限ループのON/OFF機能が未実装 リサイズ時に全体を再初期化するのは非効率 5. 保守性 マジックナンバーが多い(0.5、100px、400pxなど) 設定オプションがハードコード コメントが日本語と英語が混在

README.mdの書き直し

元ソース

Focus Carousel === ![キャプチャ](docs/capture.jpg "キャプチャ") ``` Create : 2024-08-20 Author : Yugeta.Koji ``` # Summary - カルーセルの少しグレードアップした機能版 - CSSとJavascriptでHTMLに記載した素材を簡単にセットできる仕様 - 他のライブラリなどは使わない簡易コード - 横スライドを無限にループできる仕様 - 1つのページ内に複数のカルーセルを設置可能 # Issue - スライドITEMの個数制限は? - 数が少ない個数の場合のカルーセル - 無限ループのON/OFF - ページネーションの追加(予定) - 横移動ボタンの設置(予定) - JSを All in One にした方が、サイトへの導入がしやすいかも。 # Howto Build - HTML > 以下を記述するだけ。figure直下の.itemを複数追加すると、横スライドする仕様。 ``` <div class="carousel"> <figure> <div class="item"> <img src="img/sample.jpg" alt=""> </div> </figure> </div> ``` - 各種設定 - カルーセル内のitemサイズ - 無限ループのON/OFF - Focus以外のボカシ強度 : filter:blur(**) # Demo https://yugeta.github.io/focus_carousel/src/ # Update - 2024-08-20 : First-commit
  • README.mdを書き直して
res : 3m23s # Focus Carousel ![キャプチャ](docs/capture.jpg "Focus Carousel デモ") シンプルで軽量、フォーカスエフェクト付きの画像カルーセルライブラリ ``` Create : 2024-08-20 Author : Yugeta.Koji Modified : 2026-02-19 Editor : AI Assistant (Kiro) ``` ## 特徴 - 📦 **軽量** - 外部ライブラリ不要、純粋なJavaScriptとCSS - ♿ **アクセシブル** - キーボード操作とスクリーンリーダーに対応 - 🔄 **無限ループ** - シームレスな循環スクロール(ON/OFF可能) - 🎯 **フォーカスエフェクト** - 中央のアイテムを自動的に強調表示 - 📱 **レスポンシブ** - モバイルからデスクトップまで対応 - 🎨 **カスタマイズ可能** - 設定オプションで動作を調整 - 🔢 **複数設置対応** - 1ページ内に複数のカルーセルを配置可能 ## デモ https://yugeta.github.io/focus_carousel/src/ ## インストール ファイルをダウンロードして、プロジェクトに配置してください。 ``` project/ ├── css/ │ ├── carousel.css │ └── style.css └── js/ ├── carousel.js ├── main.js └── uuid.js ``` ## 基本的な使い方 ### 1. HTMLマークアップ ```html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="stylesheet" href="css/style.css"> <script type="module" src="js/main.js"></script> </head> <body> <div class="carousel" role="region" aria-label="画像カルーセル"> <figure> <div class="item" role="group" aria-label="画像 1"> <img src="img/001.jpg" alt="画像の説明"> </div> <div class="item" role="group" aria-label="画像 2"> <img src="img/002.jpg" alt="画像の説明"> </div> <!-- 必要なだけアイテムを追加 --> </figure> </div> </body> </html> ``` ### 2. JavaScriptで初期化 デフォルト設定で使用する場合: ```javascript import { Carousel } from "./carousel.js" new Carousel() ``` オプションを指定する場合: ```javascript import { Carousel } from "./carousel.js" new Carousel({ infiniteLoop: true, // 無限ループを有効化(デフォルト: true) minItemsForLoop: 3, // ループに必要な最小アイテム数(デフォルト: 3) blurAmount: 5, // ぼかし強度(将来の実装用) focusScale: 1.5 // フォーカス時の拡大率(将来の実装用) }) ``` ## 設定オプション | オプション | 型 | デフォルト | 説明 | |-----------|-----|-----------|------| | `infiniteLoop` | boolean | `true` | 無限ループの有効/無効 | | `minItemsForLoop` | number | `3` | ループを有効にする最小アイテム数 | | `blurAmount` | number | `5` | 非フォーカス時のぼかし強度(px) | | `focusScale` | number | `1.5` | フォーカス時の拡大率 | ## 操作方法 - **マウス/タッチ** - スワイプまたはドラッグでスクロール - **キーボード** - 左右の矢印キーで移動 - **スクロールホイール** - 横スクロールで移動 ## カスタマイズ ### CSSでのスタイル調整 `src/css/carousel.css`を編集して、以下の項目をカスタマイズできます: ```css .carousel { height: 500px; /* カルーセルの高さ */ } .carousel figure { gap: 10px; /* アイテム間の間隔 */ padding: 100px 0; /* 上下のパディング */ } .carousel figure .item { width: 400px; /* アイテムの幅 */ } .carousel figure .item > * { filter: blur(5px); /* 非フォーカス時のぼかし */ } .carousel figure .item[data-status="active"] > * { transform: scale(1.5); /* フォーカス時の拡大率 */ filter: blur(0px); /* フォーカス時はぼかしなし */ } ``` ## ブラウザ対応 - Chrome(最新版) - Firefox(最新版) - Safari(最新版) - Edge(最新版) 古いブラウザでは一部機能(scrollendイベント)のフォールバックが動作します。 ## アクセシビリティ - ARIA属性による適切なセマンティクス - キーボード操作のフルサポート - スクリーンリーダー対応 - 画像のalt属性による代替テキスト ## 今後の予定 - [ ] ページネーション機能 - [ ] 前へ/次へナビゲーションボタン - [ ] 自動再生機能 - [ ] 画像の遅延読み込み - [ ] 3Dエフェクトオプション - [ ] タッチジェスチャーの最適化 ## トラブルシューティング ### アイテムが少ない場合 アイテム数が`minItemsForLoop`未満の場合、無限ループは自動的に無効になります。 ### スクロールがスムーズでない ブラウザのハードウェアアクセラレーションを有効にしてください。 ### キーボード操作が効かない カルーセル要素にフォーカスがあることを確認してください(Tabキーで移動)。 ## ライセンス MIT License ## 更新履歴 - 2024-08-20 : 初回リリース - 2024-XX-XX : アクセシビリティ対応、キーボード操作追加、設定オプション実装 ## 作者 Yugeta.Koji ## 貢献 バグ報告や機能リクエストは、Issuesでお願いします。

注意点

修正日と修正者の更新が記述されていなかったので、追加で伝えて、上記のコードが返りました。 あと、README.mdのみの変更と記述しないと、勝手にコードを書き直されてしまうので、「他のファイルを修正しないように」とプロンプトに追記しておきました。

コード修正

  • 改善案を元に、対象のファイルを修正して。
res : 44s 以下のファイルが更新されました。
modified: src/index.html modified: src/js/carousel.js modified: src/js/uuid.js

修正ポイント

index.html --- a/src/index.html +++ b/src/index.html @@ -1,39 +1,49 @@ -<link rel="stylesheet" href="css/style.css"/> -<script type="module" src="js/main.js"></script> -<meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0" name="viewport"> -<link href="img/favicon.svg" rel="icon" type="image/svg+xml"> +<!DOCTYPE html> +<html lang="ja"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0"> + <title>Focus Carousel</title> + <link rel="icon" href="img/favicon.svg" type="image/svg+xml"> + <link rel="stylesheet" href="css/style.css"> + <script type="module" src="js/main.js"></script> +</head> +<body> -<div class="carousel"> +<div class="carousel" role="region" aria-label="画像カルーセル"> <figure> - <div class="item"> - <img src="img/001.jpg" alt=""> + <div class="item" role="group" aria-label="画像 1"> + <img src="img/001.jpg" alt="画像 1"> </div> - <div class="item"> - <img src="img/002.jpg" alt=""> + <div class="item" role="group" aria-label="画像 2"> + <img src="img/002.jpg" alt="画像 2"> </div> - <div class="item"> - <img src="img/003.jpg" alt=""> + <div class="item" role="group" aria-label="画像 3"> + <img src="img/003.jpg" alt="画像 3"> </div> - <div class="item"> - <img src="img/004.jpg" alt=""> + <div class="item" role="group" aria-label="画像 4"> + <img src="img/004.jpg" alt="画像 4"> </div> - <div class="item"> - <img src="img/005.jpg" alt=""> + <div class="item" role="group" aria-label="画像 5"> + <img src="img/005.jpg" alt="画像 5"> </div> - <div class="item"> - <img src="img/006.jpg" alt=""> + <div class="item" role="group" aria-label="画像 6"> + <img src="img/006.jpg" alt="画像 6"> </div> - <div class="item"> - <img src="img/007.jpg" alt=""> + <div class="item" role="group" aria-label="画像 7"> + <img src="img/007.jpg" alt="画像 7"> </div> - <div class="item"> - <img src="img/008.jpg" alt=""> + <div class="item" role="group" aria-label="画像 8"> + <img src="img/008.jpg" alt="画像 8"> </div> - <div class="item"> - <img src="img/009.jpg" alt=""> + <div class="item" role="group" aria-label="画像 9"> + <img src="img/009.jpg" alt="画像 9"> </div> - <div class="item"> - <img src="img/010.jpg" alt=""> + <div class="item" role="group" aria-label="画像 10"> + <img src="img/010.jpg" alt="画像 10"> </div> </figure> </div> + +</body> +</html> src/js/carousel.js --- a/src/js/carousel.js +++ b/src/js/carousel.js @@ -7,15 +7,19 @@ import { Uuid } from "./uuid.js" export class Carousel{ selector = ".carousel" areas = [] + + // Configuration options + options = { + infiniteLoop: true, + minItemsForLoop: 3, + blurAmount: 5, + focusScale: 1.5 + } - constructor(){ - this.promise = new Promise((resolve, reject)=>{ - this.resolve = resolve - this.reject = reject - this.init() - this.set_event() - this.finish() - }) + constructor(options = {}){ + this.options = { ...this.options, ...options } + this.init() + this.set_event() } // 1ページ内のカルーセル領域の取得(エリア一覧) @@ -60,13 +64,49 @@ export class Carousel{ set_event(){ for(const area of this.elements){ const figure = area.querySelector(":scope > *") - figure.addEventListener("scroll" , this.scroll.bind(this)) - figure.addEventListener("scrollend" , this.scroll_end.bind(this)) + figure.addEventListener("scroll", this.scroll.bind(this)) + + // scrollend のフォールバック + if('onscrollend' in window){ + figure.addEventListener("scrollend", this.scroll_end.bind(this)) + } else { + let scrollTimeout + figure.addEventListener("scroll", () => { + clearTimeout(scrollTimeout) + scrollTimeout = setTimeout(() => this.scroll_end({ target: figure }), 150) + }) + } + + // キーボード操作のサポート + figure.addEventListener("keydown", this.handle_keyboard.bind(this)) + figure.setAttribute("tabindex", "0") + } + + let resizeTimeout + window.addEventListener("resize", () => { + clearTimeout(resizeTimeout) + resizeTimeout = setTimeout(() => this.resize(), 250) + }) + } + + // キーボード操作 + handle_keyboard(e){ + const figure = e.target + const scrollAmount = 400 // 1アイテム分 + + switch(e.key){ + case "ArrowLeft": + e.preventDefault() + figure.scrollBy({ left: -scrollAmount, behavior: 'smooth' }) + break + case "ArrowRight": + e.preventDefault() + figure.scrollBy({ left: scrollAmount, behavior: 'smooth' }) + break } - window.addEventListener("resize" , this.resize.bind(this)) } - // [イベント] カルーセルのスクリール処理 + // [イベント] カルーセルのスクロール処理 scroll(e){ this.set_status(e.target) } @@ -79,12 +119,17 @@ export class Carousel{ if(!data){return} const items = data.items for(const item of items){ - const center = Math.abs(~~(item.offsetLeft - figure.scrollLeft + (item.offsetWidth / 2)) - data.center) - if(center < item.offsetWidth/2){ - item.setAttribute("data-status" , "active") + const itemCenter = item.offsetLeft - figure.scrollLeft + (item.offsetWidth / 2) + const center = Math.abs(Math.floor(itemCenter) - data.center) + const isActive = center < item.offsetWidth / 2 + + if(isActive){ + item.setAttribute("data-status", "active") + item.setAttribute("aria-current", "true") } else if(item.hasAttribute("data-status")){ item.removeAttribute("data-status") + item.removeAttribute("aria-current") } } } @@ -96,8 +141,11 @@ export class Carousel{ // 無限ループ処理 set_loop(figure){ - if(!figure){return} + if(!figure || !this.options.infiniteLoop){return} const elms = figure.querySelectorAll(":scope > *") + + // アイテム数が少ない場合はループしない + if(elms.length < this.options.minItemsForLoop){return} // left if(figure.scrollLeft < elms[0].offsetWidth){ @@ -123,11 +171,11 @@ export class Carousel{ // 画面リサイズ時の処理 resize(){ - this.init() - } - - // 設定完了処理(.promise.then()で処理追加が可能) - finish(){ - this.resolve() + // 中心位置のみ再計算 + for(const area of this.areas){ + const rect = area.elm.getBoundingClientRect() + area.rect = rect + area.center = rect.width / 2 + } } } src/js/uuid.js --- a/src/js/uuid.js +++ b/src/js/uuid.js @@ -21,10 +21,10 @@ export class Uuid{ // hash-idの生成 make(){ const HEXOCTETS = Object.freeze( [ ...Array( 0x100 ) ].map( ( e, i ) => i.toString( 0x10 ).padStart( 2, "0" ).toUpperCase() ) ); - const VARSION = 0x40; + const VERSION = 0x40; const VARIANT = 0x80; let bytes = null; - if(crypto){ + if(typeof crypto !== 'undefined' && crypto.getRandomValues){ bytes = crypto.getRandomValues(new Uint8Array(16)); } else{ @@ -42,7 +42,7 @@ export class Uuid{ + HEXOCTETS[ bytes[ 4] ] + HEXOCTETS[ bytes[ 5] ] + "-" - + HEXOCTETS[ bytes[ 6] & 0x0f | VARSION ] + + HEXOCTETS[ bytes[ 6] & 0x0f | VERSION ] + HEXOCTETS[ bytes[ 7] ] + "-" + HEXOCTETS[ bytes[ 8] & 0x3f | VARIANT ] カラーなしで読みずらいと思いますが、git diffした結果表示です。 (スマホではほぼ読めないと思うのでPCでご覧ください)

あとがき

自分が書いたコードをAIにレビューして、修正してもらったのが、結構斬新でした。 普通、ここまで修正してくれる実際の人っていなかったから、「これが正解」とまでは考えませんが、「こういう書き方もあるんだ!」的な気付きにつながるので、学習にも繋がります。

人気の投稿

このブログを検索

ごあいさつ

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

ブログ アーカイブ