
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
===

```
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
res : 3m23s
# 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にレビューして、修正してもらったのが、結構斬新でした。
普通、ここまで修正してくれる実際の人っていなかったから、「これが正解」とまでは考えませんが、「こういう書き方もあるんだ!」的な気付きにつながるので、学習にも繋がります。
0 件のコメント:
コメントを投稿