diff --git a/DEVELOP.md b/DEVELOP.md index 119b1fa7..4d79519f 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -49,12 +49,12 @@ class => method => usecase => service `packages` ディレクトリの依存関係で注意する点は以下の通りです。 - `@next2d/core` は他の `packages` からの参照を禁止しています。 - `@next2d/events`, `@next2d/cache`, `@next2d/filters`, `@next2d/geom`, `@next2d/texture-packer`, `@next2d/render-queue` は疎結合で設計されている為、他の `packages` の `import` を禁止しています。 -- `@next2d/renderer` はOffscreenCanvasがworkerで処理されるため、 `@next2d/webgl` のみ `import` を許可しています。 +- `@next2d/renderer` はOffscreenCanvasがworkerで処理されるため、 `@next2d/webgl`,`@next2d/webgpu` のみ `import` を許可しています。 The dependencies to note in the `packages` directory are as follows - `@next2d/core` does not allow references from other `packages`. - `@next2d/events`, `@next2d/cache`, `@next2d/filters`, `@next2d/geom`, `@next2d/texture-packer` and `@next2d/render-queue` are designed to be loosely coupled, so `import` of other `packages` is prohibited. -- `@next2d/renderer` allows `import` only for `@next2d/webgl`, because OffscreenCanvas is processed by the worker. +- `@next2d/renderer` allows `import` only for `@next2d/webgl`,`@next2d/webgpu`, because OffscreenCanvas is processed by the worker. ## License This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/README.md b/README.md index 4a8e0eff..0817ecec 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,15 @@ Next2D Player [![Twitter](https://img.shields.io/twitter/follow/Next2D?style=social)](https://twitter.com/Next2D) [日本語] -Next2D Playerは、WebGLのハードウェアアクセラレーションでグラフィックス処理負荷を軽減し、OffscreenCanvasのマルチスレッド処理で描画パフォーマンスを向上させています。 +Next2D Playerは、WebGL/WebGPUのハードウェアアクセラレーションでグラフィックス処理負荷を軽減し、OffscreenCanvasのマルチスレッド処理で描画パフォーマンスを向上させています。 ベクター描画、Tweenアニメーション、テキスト、音声、動画など、さまざまな要素をサポートしているので、ゲーム制作、インタラクティブなデータビジュアライゼーション、クリエイティブなウェブアプリケーションなど、豊かな表現が必要とされるプロジェクトで活用が期待できます。 [English] -Next2D Player reduces graphics processing load with WebGL hardware acceleration and improves drawing performance with OffscreenCanvas multi-threaded processing. +Next2D Player reduces graphics processing load with WebGL/WebGPU hardware acceleration and improves drawing performance with OffscreenCanvas multi-threaded processing. With support for vector rendering, tween animation, text, audio, video, and many other elements, Next2D Player can be used for game production, interactive data visualization, creative web applications, and other projects that require rich expression. The software is expected to be used in game production, interactive data visualization, creative web applications and other projects requiring rich expression. [简体中文] -Next2D Player通过WebGL硬件加速降低了图形处理负载,通过OffscreenCanvas多线程处理提高了绘图性能。 +Next2D Player通过WebGL/WebGPU硬件加速降低了图形处理负载,通过OffscreenCanvas多线程处理提高了绘图性能。 由于支持矢量绘图、Tween动画、文本、音频、视频和许多其他元素,它可用于游戏制作、交互式数据可视化、创意网络应用和其他需要丰富表达的项目。 该软件可用于需要丰富表现力的项目中。 ## Support diff --git a/package.json b/package.json index b8ae4f49..ba1bde9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@next2d/player", - "version": "3.0.0", + "version": "3.0.1", "description": "Experience the fast and beautiful anti-aliased rendering of WebGL. You can create rich, interactive graphics, cross-platform applications and games without worrying about browser or device compatibility.", "author": "Toshiyuki Ienaga (https://github.com/ienaga/)", "license": "MIT", diff --git a/packages/display/README.md b/packages/display/README.md index ddca2227..164a6978 100644 --- a/packages/display/README.md +++ b/packages/display/README.md @@ -38,8 +38,6 @@ src/ ├── MovieClip.ts # Timeline-based animation object / タイムラインベースのアニメーションオブジェクト ├── Shape.ts # Lightweight graphics display object / 軽量なグラフィックス表示オブジェクト ├── Stage.ts # Root display object / ルート表示オブジェクト -├── TextField.ts # Text display and input / テキスト表示と入力 -├── Video.ts # Video display object / ビデオ表示オブジェクト │ ├── Graphics.ts # Vector drawing API / ベクター描画 API ├── Graphics/ # Graphics implementation details / Graphics 実装の詳細 @@ -116,8 +114,6 @@ classDiagram DisplayObjectContainer <|-- Stage Sprite <|-- MovieClip DisplayObject <|-- Shape - DisplayObject <|-- TextField - DisplayObject <|-- Video class EventDispatcher { +addEventListener() @@ -201,18 +197,6 @@ classDiagram +frameRate: number +color: number } - - class TextField { - +text: string - +htmlText: string - +textColor: number - +autoSize: string - } - - class Video { - +videoWidth: number - +videoHeight: number - } ``` ## Graphics API / Graphics API @@ -378,8 +362,8 @@ This architecture ensures: - `@next2d/events` - Event system / イベントシステム - `@next2d/geom` - Geometric primitives / 幾何プリミティブ - `@next2d/filters` - Display filters / 表示フィルター -- `@next2d/text` - Text rendering / テキストレンダリング -- `@next2d/media` - Media playback / メディア再生 +- `@next2d/text` - Text rendering (includes TextField) / テキストレンダリング(TextFieldを含む) +- `@next2d/media` - Media playback (includes Video) / メディア再生(Videoを含む) - `@next2d/net` - Network communication / ネットワーク通信 - `@next2d/ui` - User interface components / ユーザーインターフェースコンポーネント diff --git a/packages/filters/README.md b/packages/filters/README.md index f77f3191..0eb367c9 100644 --- a/packages/filters/README.md +++ b/packages/filters/README.md @@ -7,10 +7,10 @@ ## Overview / 概要 **English:** -The `@next2d/filters` package provides GPU-accelerated visual effect filters for DisplayObjects in the Next2D player. This package includes a comprehensive set of bitmap filter effects that can be applied to any display object, offering high-performance image processing capabilities powered by WebGL. +The `@next2d/filters` package provides GPU-accelerated visual effect filters for DisplayObjects in the Next2D player. This package includes a comprehensive set of bitmap filter effects that can be applied to any display object, offering high-performance image processing capabilities powered by WebGL/WebGPU. **日本語:** -`@next2d/filters` パッケージは、Next2D プレイヤーの DisplayObject に対して GPU アクセラレーションによる視覚エフェクトフィルターを提供します。このパッケージには、任意の表示オブジェクトに適用可能なビットマップフィルター効果の包括的なセットが含まれており、WebGL によって高速化された画像処理機能を提供します。 +`@next2d/filters` パッケージは、Next2D プレイヤーの DisplayObject に対して GPU アクセラレーションによる視覚エフェクトフィルターを提供します。このパッケージには、任意の表示オブジェクトに適用可能なビットマップフィルター効果の包括的なセットが含まれており、WebGL/WebGPU によって高速化された画像処理機能を提供します。 ## Installation / インストール @@ -237,13 +237,13 @@ displayObject.filters = [blur, glow, shadow]; ## Performance Considerations / パフォーマンスに関する考慮事項 **English:** -- All filters are GPU-accelerated using WebGL shaders +- All filters are GPU-accelerated using WebGL/WebGPU shaders - Filter quality settings affect performance and visual output - Multiple filters are processed in sequence - Bounds calculation is optimized for minimal overhead **日本語:** -- すべてのフィルターは WebGL シェーダーを使用して GPU アクセラレーションされています +- すべてのフィルターは WebGL/WebGPU シェーダーを使用して GPU アクセラレーションされています - フィルターの品質設定はパフォーマンスと視覚出力に影響します - 複数のフィルターは順番に処理されます - バウンズ計算は最小限のオーバーヘッドに最適化されています diff --git a/packages/text/src/interface/ITextFieldCharacter copy.ts b/packages/text/src/interface/ITextFieldCharacter copy.ts deleted file mode 100644 index f3c9837e..00000000 --- a/packages/text/src/interface/ITextFieldCharacter copy.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { ITextFormatAlign } from "./ITextFormatAlign"; -import type { ITextFieldType } from "./ITextFieldType"; -import type { IBounds } from "./IBounds"; - -export interface ITextFieldCharacter { - symbol?: string; - extends: string; - font: string; - size: number; - align: ITextFormatAlign; - color: number; - leading: number; - letterSpacing: number; - leftMargin: number; - rightMargin: number; - fontType: number; - autoSize: number; - inputType: ITextFieldType; - multiline: boolean; - wordWrap: boolean; - border: boolean; - scroll: boolean; - thickness: number; - thicknessColor: number; - bounds: IBounds; - text: string; -} \ No newline at end of file diff --git a/packages/text/src/interface/ITextFormatAlign copy.ts b/packages/text/src/interface/ITextFormatAlign copy.ts deleted file mode 100644 index aa48dea1..00000000 --- a/packages/text/src/interface/ITextFormatAlign copy.ts +++ /dev/null @@ -1 +0,0 @@ -export type ITextFormatAlign = "center" | "left" | "right"; \ No newline at end of file diff --git a/packages/webgpu/README.md b/packages/webgpu/README.md index 06d108f8..98fe45a1 100644 --- a/packages/webgpu/README.md +++ b/packages/webgpu/README.md @@ -1,574 +1,233 @@ # @next2d/webgpu -WebGPU-based rendering engine for Next2D (Experimental / Work in Progress) - -WebGPU ベースのレンダリングエンジン(実験的 / 開発中) - ---- - -## ⚠️ Warning / 警告 - -**This package is currently under development and NOT production-ready.** - -**本パッケージは現在開発中であり、本番環境での使用には対応していません。** - -- Many features are incomplete or placeholder implementations -- APIs may change without notice -- Performance has not been optimized -- Testing is incomplete - ---- +WebGPU-based rendering engine for Next2D Player / Next2D Player用のWebGPUベースレンダリングエンジン ## Overview / 概要 -This package provides a WebGPU-based rendering backend for Next2D Player, designed as an alternative to the existing WebGL implementation. WebGPU is a modern graphics API that offers better performance and more control over GPU resources. +The `@next2d/webgpu` package is a rendering backend for Next2D Player, providing a high-performance, shader-based drawing pipeline built on WebGPU. This package handles all graphics rendering operations including vector shapes, bitmaps, filters, and effects. -本パッケージは Next2D Player 向けの WebGPU ベースのレンダリングバックエンドを提供します。既存の WebGL 実装の代替として設計されており、WebGPU は優れたパフォーマンスと GPU リソースに対するより細かい制御を提供する最新のグラフィックス API です。 +`@next2d/webgpu`パッケージは、Next2D Playerのレンダリングバックエンドであり、WebGPU上に構築された高性能なシェーダーベースの描画パイプラインを提供します。このパッケージは、ベクター図形、ビットマップ、フィルター、エフェクトなど、すべてのグラフィックスレンダリング操作を処理します。 -### Key Features / 主な機能 +### Key Features / 主な特徴 -- WGSL (WebGPU Shading Language) shader implementations -- Texture atlas management for efficient rendering -- Instance-based batch rendering -- Blend mode support -- Mask rendering capabilities -- Filter effects (Blur, Glow, Drop Shadow, Color Matrix) +- **WebGPU-based rendering** - Hardware-accelerated graphics using modern WebGPU API / WebGPU APIを使用したハードウェアアクセラレーション +- **Shader-based pipeline** - Efficient WGSL shader processing for all rendering operations / すべてのレンダリング操作に対する効率的なWGSLシェーダー処理 +- **Texture atlas management** - Optimized texture packing and management / 最適化されたテクスチャパッキングと管理 +- **Advanced filters** - Full support for color matrix, blur, glow, displacement map and more / カラーマトリックス、ブラー、グロー、ディスプレースメントマップなどのフルサポート +- **Blend modes** - Multiple blend mode support with stencil operations / ステンシル操作による複数のブレンドモードサポート +- **Masking system** - Stencil buffer-based masking for complex clipping / 複雑なクリッピングのためのステンシルバッファベースのマスキング +- **Compute shaders** - GPU compute-based blur processing for high-performance filter effects / 高性能フィルターエフェクトのためのGPUコンピュートベースのブラー処理 ---- +## Installation / インストール + +```bash +npm install @next2d/webgpu +``` ## Directory Structure / ディレクトリ構造 ``` src/ -├── Context.ts # Main rendering context (WebGPU版のメインコンテキスト) -├── WebGPUUtil.ts # Utility functions and global state management -│ -├── Managers / マネージャー -│ ├── AtlasManager.ts # Atlas texture management (アトラステクスチャ管理) -│ ├── AttachmentManager.ts # Offscreen attachment/FBO management (オフスクリーンアタッチメント管理) -│ ├── BufferManager.ts # Vertex/Uniform buffer management (バッファ管理) -│ ├── DrawManager.ts # Drawing operations helper (描画操作ヘルパー) -│ ├── FrameBufferManager.ts # Framebuffer management (フレームバッファ管理) -│ └── TextureManager.ts # Texture and sampler management (テクスチャ/サンプラー管理) -│ -├── Core Components / コアコンポーネント -│ ├── PathCommand.ts # Path drawing commands (moveTo, lineTo, bezierCurveTo, etc.) -│ │ ├── PathCommandState.ts # Path command state management -│ │ ├── service/ -│ │ │ ├── PathCommandBeginPathService.ts -│ │ │ ├── PathCommandEqualsToLastPointService.ts -│ │ │ ├── PathCommandPushCurrentPathToVerticesService.ts -│ │ │ └── PathCommandPushPointToCurrentPathService.ts -│ │ └── usecase/ -│ │ ├── PathCommandArcUseCase.ts -│ │ ├── PathCommandBezierCurveToUseCase.ts -│ │ ├── PathCommandClosePathUseCase.ts -│ │ ├── PathCommandLineToUseCase.ts -│ │ ├── PathCommandMoveToUseCase.ts -│ │ └── PathCommandQuadraticCurveToUseCase.ts -│ ├── Mesh.ts # Mesh data structures and utilities -│ │ ├── service/ -│ │ │ ├── MeshCalculateNormalVectorService.ts -│ │ │ ├── MeshFillGenerateService.ts -│ │ │ ├── MeshGetQuadraticBezierPointService.ts -│ │ │ ├── MeshGetQuadraticBezierTangentService.ts -│ │ │ └── MeshLerpService.ts -│ │ └── usecase/ -│ │ ├── MeshBitmapStrokeGenerateUseCase.ts -│ │ ├── MeshFillGenerateUseCase.ts -│ │ ├── MeshGradientStrokeGenerateUseCase.ts -│ │ ├── MeshSplitQuadraticBezierUseCase.ts -│ │ └── MeshStrokeGenerateUseCase.ts -│ ├── BezierConverter.ts # Bezier curve conversion utilities -│ │ ├── service/ -│ │ │ ├── BezierConverterCubicToQuadService.ts -│ │ │ └── BezierConverterSplitCubicService.ts -│ │ └── usecase/ -│ │ └── BezierConverterAdaptiveCubicToQuadUseCase.ts -│ ├── Blend.ts # Blend mode state management -│ ├── Mask.ts # Mask rendering state management -│ └── Grid.ts # Grid/9-slice system -│ -├── Shader/ シェーダー関連 -│ ├── ShaderSource.ts # WGSL shader source code -│ ├── PipelineManager.ts # Render pipeline management -│ ├── ShaderInstancedManager.ts # Instance rendering shader management -│ ├── BlendModeShader.ts # Blend mode shader implementations -│ └── GradientLUTGenerator.ts # Gradient lookup table generation -│ ├── service/ -│ │ ├── GradientLUTCalculateResolutionService.ts -│ │ ├── GradientLUTGeneratePixelsService.ts -│ │ ├── GradientLUTInterpolateColorService.ts -│ │ └── GradientLUTParseStopsService.ts -│ └── usecase/ -│ └── GradientLUTGenerateDataUseCase.ts -│ -├── Gradient/ グラデーション関連 -│ ├── GradientLUTCache.ts # Gradient LUT cache management -│ └── GradientLUTGenerator.ts # Gradient LUT generation -│ -├── Filter/ フィルター実装 -│ ├── index.ts # Filter exports -│ ├── BlurFilterShader.ts # Blur filter implementation -│ ├── BlurFilterUseCase.ts # Blur filter use case -│ ├── GlowFilterShader.ts # Glow filter implementation -│ ├── DropShadowFilterShader.ts # Drop shadow filter implementation -│ ├── ColorMatrixFilterShader.ts # Color matrix filter implementation -│ ├── BevelFilter/ -│ │ └── FilterApplyBevelFilterUseCase.ts -│ ├── BlurFilter/ -│ │ └── FilterApplyBlurFilterUseCase.ts -│ ├── ColorMatrixFilter/ -│ │ └── FilterApplyColorMatrixFilterUseCase.ts -│ ├── ConvolutionFilter/ -│ │ └── FilterApplyConvolutionFilterUseCase.ts -│ ├── DisplacementMapFilter/ -│ │ └── FilterApplyDisplacementMapFilterUseCase.ts -│ ├── DropShadowFilter/ -│ │ └── FilterApplyDropShadowFilterUseCase.ts -│ ├── GlowFilter/ -│ │ └── FilterApplyGlowFilterUseCase.ts -│ ├── GradientBevelFilter/ -│ │ └── FilterApplyGradientBevelFilterUseCase.ts -│ └── GradientGlowFilter/ -│ └── FilterApplyGradientGlowFilterUseCase.ts -│ -├── Blend/ ブレンド関連 -│ ├── BlendInstancedManager.ts # Instance-based blend rendering -│ ├── service/ -│ │ ├── BlendAddService.ts -│ │ ├── BlendAlphaService.ts -│ │ ├── BlendEraseService.ts -│ │ ├── BlendGetStateService.ts -│ │ ├── BlendOneZeroService.ts -│ │ ├── BlendResetService.ts -│ │ ├── BlendScreenService.ts -│ │ └── BlendSetModeService.ts -│ └── usecase/ -│ ├── BlendApplyComplexBlendUseCase.ts -│ └── BlendOperationUseCase.ts -│ -├── Mask/ マスク関連 -│ ├── service/ -│ │ ├── MaskBeginMaskService.ts -│ │ ├── MaskEndMaskService.ts -│ │ ├── MaskSetMaskBoundsService.ts -│ │ └── MaskUnionMaskService.ts -│ └── usecase/ -│ ├── MaskLeaveMaskUseCase.ts -│ └── MaskBindUseCase.ts -│ -└── interface/ 型定義 - ├── IAttachmentObject.ts # Attachment object interface - ├── IBlendMode.ts # Blend mode types - ├── IBounds.ts # Bounds rectangle interface - ├── IFillType.ts # Fill type definitions - ├── IPath.ts # Path interface - ├── IPoint.ts # Point interface - └── ITextureObject.ts # Texture object interface +├── Context.ts # Main rendering context / メインレンダリングコンテキスト +├── Context/ +│ ├── service/ # Context services / コンテキストサービス +│ └── usecase/ # Context use cases / コンテキストユースケース +├── AtlasManager.ts # Texture atlas management / テクスチャアトラス管理 +├── FrameBufferManager.ts # Render target management / レンダーターゲット管理 +├── AttachmentManager.ts # Color/stencil attachment management / カラー/ステンシルアタッチメント管理 +├── BufferManager.ts # Vertex/Uniform buffer management / 頂点/Uniformバッファ管理 +├── TextureManager.ts # Texture management / テクスチャ管理 +├── TexturePool.ts # Temporary texture pooling / 一時テクスチャプーリング +├── FillTexturePool.ts # Fill/gradient texture pooling / 塗り/グラデーションテクスチャプーリング +├── SamplerCache.ts # GPU sampler caching / GPUサンプラーキャッシュ +├── Blend.ts # Blend mode handling / ブレンドモード処理 +├── Blend/ +│ └── BlendInstancedManager.ts # Blend instanced rendering / ブレンドインスタンスレンダリング +├── Mask.ts # Stencil-based masking / ステンシルベースのマスキング +├── Mesh/ # Mesh generation / メッシュ生成 +│ ├── service/ # Mesh services / メッシュサービス +│ └── usecase/ # Mesh use cases (fill, stroke, gradient stroke) / メッシュユースケース +├── PathCommand.ts # Path command processing / パスコマンド処理 +├── Compute/ # GPU compute shader execution / GPUコンピュートシェーダー実行 +│ ├── ComputePipelineManager.ts # Compute pipeline management / コンピュートパイプライン管理 +│ └── ComputeExecuteBlurService.ts # Blur compute execution / ブラーコンピュート実行 +├── Filter/ # Filter implementations / フィルター実装 +│ ├── FilterGradientLUTCache.ts # Gradient LUT cache / グラデーションLUTキャッシュ +│ ├── FilterOffset.ts # Filter offset calculation / フィルターオフセット計算 +│ ├── BitmapFilterShader.ts # Bitmap filter shader / ビットマップフィルターシェーダー +│ ├── BevelFilter/ # Bevel filter / ベベルフィルター +│ ├── BlurFilter/ # Blur filter / ブラーフィルター +│ ├── ColorMatrixFilter/ # Color matrix filter / カラーマトリックスフィルター +│ ├── ConvolutionFilter/ # Convolution filter / コンボリューションフィルター +│ ├── DisplacementMapFilter/ # Displacement map filter / ディスプレースメントマップフィルター +│ ├── DropShadowFilter/ # Drop shadow filter / ドロップシャドウフィルター +│ ├── GlowFilter/ # Glow filter / グローフィルター +│ ├── GradientBevelFilter/ # Gradient bevel filter / グラデーションベベルフィルター +│ └── GradientGlowFilter/ # Gradient glow filter / グラデーショングローフィルター +├── Gradient/ +│ └── GradientLUTGenerator.ts # Gradient LUT generation and caching / グラデーションLUT生成とキャッシュ +├── Shader/ # WGSL shader system / WGSLシェーダーシステム +│ ├── PipelineManager.ts # Render pipeline management / レンダーパイプライン管理 +│ ├── ShaderSource.ts # Shader source management / シェーダーソース管理 +│ ├── ShaderInstancedManager.ts # Instanced rendering manager / インスタンスレンダリング管理 +│ ├── BlendModeShader.ts # Blend mode shader definitions / ブレンドモードシェーダー定義 +│ ├── GradientLUTGenerator/ # Gradient LUT generation / グラデーションLUT生成 +│ │ ├── service/ # LUT generation services / LUT生成サービス +│ │ └── usecase/ # LUT generation use cases / LUT生成ユースケース +│ └── wgsl/ # WGSL shader sources / WGSLシェーダーソース +│ ├── vertex/ # Vertex shaders / バーテックスシェーダー +│ ├── fragment/ # Fragment shaders / フラグメントシェーダー +│ └── common/ # Shared WGSL utilities / 共有WGSLユーティリティ +├── Grid.ts # Grid system for rendering / レンダリング用グリッドシステム +├── BezierConverter/ # Bezier curve conversion / ベジェ曲線変換 +│ ├── BezierConverter.ts # Main class / メインクラス +│ ├── service/ # BezierConverter services / サービス +│ └── usecase/ # BezierConverter use cases / ユースケース +├── TexturePool/ # Temporary texture pooling services / 一時テクスチャプーリングサービス +│ ├── service/ # TexturePool services / サービス +│ └── usecase/ # TexturePool use cases / ユースケース +├── WebGPUUtil.ts # WebGPU utility functions / WebGPUユーティリティ関数 +└── interface/ # TypeScript interfaces / TypeScript インターフェース ``` ---- - -## Implementation Status / 実装状況 - -### ✅ Implemented / 実装済み - -#### Core Rendering / コア描画機能 -- ✅ Basic initialization and device setup (基本的な初期化とデバイスセットアップ) -- ✅ Canvas context configuration (キャンバスコンテキストの設定) -- ✅ Frame lifecycle management (beginFrame/endFrame) (フレームライフサイクル管理) -- ✅ Transform matrix operations (save/restore/setTransform/transform) (変換行列操作) -- ✅ Background color fill (背景色の塗りつぶし) -- ✅ Resize handling (リサイズ処理) - -#### Path Drawing / パス描画 -- ✅ Path commands (beginPath, moveTo, lineTo, closePath) (パスコマンド) -- ✅ Bezier curves (quadraticCurveTo, bezierCurveTo) (ベジェ曲線) -- ✅ Arc drawing (円弧描画) -- ✅ Fill operations with solid colors (単色塗りつぶし) -- ✅ Stroke operations with mesh generation (ストローク描画とメッシュ生成) -- ✅ Vertex triangulation for path filling (パス塗りつぶし用の頂点三角形分割) - -#### Texture & Atlas Management / テクスチャ・アトラス管理 -- ✅ Atlas texture creation (4096x4096) (アトラステクスチャ作成) -- ✅ Node allocation in texture atlas (テクスチャアトラスのノード割り当て) -- ✅ Texture from pixels/ImageBitmap (ピクセル/ImageBitmapからのテクスチャ作成) -- ✅ Sampler creation (linear, nearest, repeat) (サンプラー作成) -- ✅ Texture pool management (テクスチャプール管理) - -#### Buffer Management / バッファ管理 -- ✅ Vertex buffer creation and management (頂点バッファ作成と管理) -- ✅ Uniform buffer creation and updates (Uniformバッファ作成と更新) -- ✅ Rectangle vertex generation (矩形頂点生成) - -#### Offscreen Rendering / オフスクリーンレンダリング -- ✅ Attachment object pool (アタッチメントオブジェクトプール) -- ✅ Bind/unbind attachment operations (アタッチメントのバインド/アンバインド) -- ✅ Render target switching (レンダーターゲットの切り替え) -- ✅ Stencil texture creation (ステンシルテクスチャ作成) - -#### Shader Pipelines / シェーダーパイプライン -- ✅ Fill pipeline (solid color) (単色塗りつぶしパイプライン) -- ✅ Mask pipeline (Bezier curve anti-aliasing) (マスクパイプライン - ベジェ曲線アンチエイリアス) -- ✅ Basic pipeline (基本パイプライン) -- ✅ Texture pipeline (テクスチャパイプライン) -- ✅ Instanced rendering pipeline (インスタンス描画パイプライン) -- ✅ Gradient pipeline structure (グラデーションパイプライン構造) -- ✅ Blend mode pipeline structure (ブレンドモードパイプライン構造) - -#### Instance Rendering / インスタンス描画 -- ✅ Instance data management (インスタンスデータ管理) -- ✅ Display object to instance array conversion (表示オブジェクトのインスタンス配列変換) -- ✅ Batch rendering with instancing (インスタンシングによるバッチ描画) -- ✅ Color transform (multiply/add) (カラー変換 - 乗算/加算) - -#### Image Operations / 画像操作 -- ✅ Draw pixels to atlas node (アトラスノードへのピクセル描画) -- ✅ Draw OffscreenCanvas/ImageBitmap to atlas (OffscreenCanvas/ImageBitmapのアトラス描画) -- ✅ Create ImageBitmap from GPU texture (GPUテクスチャからのImageBitmap作成) -- ✅ Premultiplied alpha conversion (プリマルチプライドアルファ変換) - -### 🚧 Partially Implemented / 部分的に実装 - -#### Drawing Operations / 描画操作 -- 🚧 Gradient fill (gradientFill) - Placeholder, falls back to solid fill - - グラデーション塗りつぶし - プレースホルダー実装、単色塗りつぶしにフォールバック -- 🚧 Bitmap fill (bitmapFill) - Texture creation works, shader integration pending - - ビットマップ塗りつぶし - テクスチャ作成は動作、シェーダー統合は保留 -- 🚧 Gradient stroke (gradientStroke) - Placeholder - - グラデーションストローク - プレースホルダー実装 -- 🚧 Bitmap stroke (bitmapStroke) - Placeholder - - ビットマップストローク - プレースホルダー実装 - -#### Masking / マスク処理 -- 🚧 Clip operations (clip) - Basic structure, stencil buffer integration needed - - クリッピング操作 - 基本構造はあるが、ステンシルバッファ統合が必要 -- 🚧 Mask begin/end (beginMask, endMask, setMaskBounds, leaveMask) - Service layer exists - - マスク開始/終了 - サービス層は存在 - -#### Filters / フィルター -- 🚧 applyFilter - Framework exists, filter shaders created but not integrated - - フィルター適用 - フレームワークは存在、フィルターシェーダーは作成済みだが統合されていない - - BlurFilterShader, GlowFilterShader, DropShadowFilterShader, ColorMatrixFilterShader - -### ❌ TODO / 未実装 - -#### Core Features / コア機能 -- ❌ Cache clearing implementation (resize時のキャッシュクリア) -- ❌ clearRect with scissor/clear operations (シザー/クリア操作によるclearRect) -- ❌ 9-slice grid transformation (useGrid) (9スライスグリッド変換) - -#### Advanced Rendering / 高度なレンダリング -- ❌ Complete gradient LUT texture generation (完全なグラデーションLUTテクスチャ生成) -- ❌ Gradient shader parameter passing (グラデーションシェーダーのパラメータ渡し) -- ❌ Bitmap fill/stroke shader integration (ビットマップ塗りつぶし/ストロークシェーダー統合) -- ❌ Stencil buffer-based clipping (ステンシルバッファベースのクリッピング) -- ❌ Two-pass rendering for masks (マスク用の2パスレンダリング) - -#### Blend Modes / ブレンドモード -- ❌ Full blend mode integration (multiply, screen, add, etc.) - - 完全なブレンドモード統合(乗算、スクリーン、加算など) -- ❌ Advanced blend modes (overlay, hard-light, soft-light, etc.) - - 高度なブレンドモード(オーバーレイ、ハードライト、ソフトライトなど) - -#### Filters / フィルター -- ❌ Filter parameter binding and execution (フィルターパラメータバインディングと実行) -- ❌ Multi-pass filter rendering (複数パスフィルターレンダリング) -- ❌ Convolution filter (コンボリューションフィルター) -- ❌ Displacement map filter (ディスプレイスメントマップフィルター) - -#### Optimization / 最適化 -- ❌ Buffer reuse and pooling optimization (バッファ再利用とプール最適化) -- ❌ Command encoder reuse (コマンドエンコーダー再利用) -- ❌ Pipeline state caching (パイプライン状態キャッシング) -- ❌ Batch draw call optimization (バッチ描画コール最適化) - -#### Testing & Documentation / テストとドキュメント -- ❌ Unit tests (ユニットテスト) -- ❌ Integration tests (統合テスト) -- ❌ Performance benchmarks (パフォーマンスベンチマーク) -- ❌ API documentation (API ドキュメント) - ---- - -## Context.ts - Implementation Analysis / Context.ts 実装分析 - -The `Context.ts` file is the main entry point for the WebGPU rendering engine. Here's a detailed breakdown of its implementation status: - -`Context.ts` ファイルは WebGPU レンダリングエンジンのメインエントリーポイントです。実装状況の詳細な内訳は以下の通りです: - -### Fully Implemented Methods / 完全実装済みメソッド - -| Method | Status | Notes | -|--------|--------|-------| -| `constructor` | ✅ Complete | Device, context, format initialization | -| `save` / `restore` | ✅ Complete | Matrix stack operations | -| `setTransform` / `transform` | ✅ Complete | 2D transformation matrix | -| `reset` | ✅ Complete | Reset context state | -| `beginPath` / `moveTo` / `lineTo` | ✅ Complete | Path command delegation | -| `quadraticCurveTo` / `bezierCurveTo` | ✅ Complete | Bezier curve support | -| `arc` / `closePath` | ✅ Complete | Path operations | -| `fillStyle` / `strokeStyle` | ✅ Complete | Color style setters | -| `fill` | ✅ Complete | Solid color fill with pipeline | -| `stroke` | ✅ Complete | Stroke with mesh generation | -| `updateBackgroundColor` | ✅ Complete | Background color update | -| `fillBackgroundColor` | ✅ Complete | Clear with background color | -| `resize` | ✅ Complete | Canvas resize (cache clear TODO) | -| `beginFrame` / `endFrame` | ✅ Complete | Frame lifecycle management | -| `bindAttachment` / `unbindAttachment` | ✅ Complete | Offscreen rendering | -| `getAttachmentObject` / `releaseAttachment` | ✅ Complete | Attachment management | -| `createNode` / `removeNode` | ✅ Complete | Atlas node management | -| `drawPixels` / `drawElement` | ✅ Complete | Pixel/element to atlas | -| `drawDisplayObject` | ✅ Complete | Instance array addition | -| `drawArraysInstanced` | ✅ Complete | Batch instance rendering | -| `clearArraysInstanced` | ✅ Complete | Clear instance data | -| `createImageBitmap` | ✅ Complete | GPU→ImageBitmap conversion | -| `beginMask` / `setMaskBounds` / `endMask` / `leaveMask` | ✅ Complete | Mask service delegation | - -### Placeholder / Incomplete Methods / プレースホルダー/不完全なメソッド - -| Method | Status | Notes | -|--------|--------|-------| -| `clearRect` | 🚧 Partial | Has console.log, needs scissor+clear implementation | -| `gradientFill` | 🚧 Placeholder | console.log + falls back to fill() | -| `bitmapFill` | 🚧 Partial | Creates texture but falls back to fill() | -| `gradientStroke` | 🚧 Placeholder | console.log + falls back to stroke() | -| `bitmapStroke` | 🚧 Placeholder | console.log + falls back to stroke() | -| `clip` | 🚧 Placeholder | console.log + falls back to fill() | -| `useGrid` | 🚧 Placeholder | console.log, 9-slice not implemented | -| `applyFilter` | 🚧 Placeholder | console.log, filter shaders not integrated | - -### Debug Markers / デバッグマーカー - -The code contains multiple `console.log` statements indicating work-in-progress areas: - -コードには開発中の領域を示す複数の `console.log` 文が含まれています: - -- Line 250: `clearRect()` - "TODO: シザーとクリアを使用した実装" -- Line 228: `resize()` - "TODO: キャッシュクリア実装" -- Line 270: `clearRect()` - "TODO: シザーとクリアを使用した実装" -- Line 781: `gradientFill()` - "TODO: グラデーションLUTテクスチャを生成" -- Line 790: `gradientFill()` - "TODO: グラデーション用のシェーダーを使用" -- Line 847: `bitmapFill()` - "TODO: ビットマップ塗りつぶし用のシェーダーを使用" -- Line 876: `gradientStroke()` - "TODO: グラデーションストローク実装" -- Line 901: `bitmapStroke()` - "TODO: ビットマップストローク実装" -- Line 918: `clip()` - "TODO: ステンシルバッファを使用したクリッピング実装" -- Line 962: `useGrid()` - "TODO: Grid/9-slice transformation implementation" -- Line 1312-1320: `applyFilter()` - Multiple filter TODOs -- Line 1660: `leaveMask()` - "TODO: WebGPU版のインスタンス描画を実装後に追加" - ---- - -## Shader Implementation / シェーダー実装 - -### WGSL Shaders in ShaderSource.ts / ShaderSource.ts の WGSL シェーダー - -The package includes complete WGSL shader implementations for: - -パッケージには以下の完全な WGSL シェーダー実装が含まれています: - -1. **Fill Shader** (単色塗りつぶし) - - WebGL-compatible vertex transformation - - Premultiplied alpha blending - - Viewport normalization - -2. **Mask Shader** (マスク) - - Bezier curve rendering with anti-aliasing - - Partial derivative-based edge smoothing - -3. **Texture Shader** (テクスチャ) - - Sampled texture rendering - - Color modulation - -4. **Instanced Shader** (インスタンス描画) - - Per-instance transformation matrices - - Color transform (multiply + add) - - Atlas texture sampling - - Unpremultiply → transform → premultiply workflow - -5. **Gradient Shader** (グラデーション) - Structure only - - Linear/Radial gradient support - - LUT-based color lookup - -6. **Blend Shader** (ブレンド) - Structure only - - Normal, Multiply, Screen, Add modes - - Dual texture sampling - ---- - -## Pipeline Architecture / パイプラインアーキテクチャ - -The `PipelineManager` creates and manages 6 render pipelines: - -`PipelineManager` は 6 つのレンダーパイプラインを作成・管理します: - -1. **fill** - Solid color fill (単色塗りつぶし) -2. **mask** - Stencil/clip operations (ステンシル/クリップ操作) -3. **basic** - Simple color rendering (シンプルカラーレンダリング) -4. **texture** - Textured quad rendering (テクスチャ付き矩形レンダリング) -5. **instanced** - Batch instance rendering (バッチインスタンス描画) -6. **gradient** - Gradient fill (グラデーション塗りつぶし) - Not yet integrated -7. **blend** - Blend mode operations (ブレンドモード操作) - Not yet integrated - -All pipelines use: -- Premultiplied alpha blending -- Triangle list topology -- No backface culling - -すべてのパイプラインは以下を使用: -- プリマルチプライドアルファブレンディング -- トライアングルリストトポロジー -- バックフェースカリング無効 +## Rendering Pipeline / レンダリングパイプライン + +The WebGPU package implements a sophisticated rendering pipeline that processes graphics commands through multiple stages: + +WebGPUパッケージは、複数のステージを通じてグラフィックスコマンドを処理する洗練されたレンダリングパイプラインを実装しています: + +```mermaid +flowchart TB + Start([Start Rendering]) + Context[Context
レンダリングコンテキスト] + PathCommand[PathCommand
パスコマンド処理] + Mesh[Mesh
メッシュ生成] + Shader[Shader
シェーダー実行] + FrameBuffer[FrameBuffer
レンダーターゲット] + Atlas[AtlasManager
テクスチャアトラス] + Filter[Filter
フィルター処理] + Blend[Blend
ブレンド処理] + Mask[Mask
マスク処理] + Output([Output to Canvas]) + + Start --> Context + Context --> PathCommand + PathCommand --> Mesh + Mesh --> Shader + Shader --> FrameBuffer + + Context -.-> Atlas + Atlas -.-> Shader + + FrameBuffer --> Filter + Filter --> Blend + Blend --> Mask + Mask --> Output + + style Context fill:#e1f5ff + style Shader fill:#fff4e1 + style FrameBuffer fill:#f0e1ff + style Atlas fill:#e1ffe1 +``` ---- +### Pipeline Stages / パイプラインステージ -## Known Limitations / 既知の制限事項 +1. **Context**: Main rendering context that manages the WebGPU state and coordinates all rendering operations + - **コンテキスト**: WebGPU状態を管理し、すべてのレンダリング操作を調整するメインレンダリングコンテキスト -1. **Stencil Operations** - Depth-stencil attachment configuration incomplete - - ステンシル操作 - Depth-stencilアタッチメント設定が不完全 +2. **PathCommand**: Processes vector path commands (moveTo, lineTo, curveTo, etc.) into renderable primitives + - **パスコマンド**: ベクターパスコマンド(moveTo、lineTo、curveToなど)をレンダリング可能なプリミティブに処理 -2. **Filter Effects** - Shader code exists but parameter passing not implemented - - フィルター効果 - シェーダーコードは存在するがパラメータ渡しが未実装 +3. **Mesh**: Generates triangle meshes from path data for fills and strokes + - **メッシュ**: 塗りと線のパスデータから三角形メッシュを生成 -3. **Blend Modes** - Only normal blend mode fully functional - - ブレンドモード - ノーマルブレンドモードのみ完全に機能 +4. **Shader**: Executes WGSL shaders to render meshes with appropriate materials and effects + - **シェーダー**: 適切なマテリアルとエフェクトでメッシュをレンダリングするWGSLシェーダーを実行 -4. **Gradient Rendering** - LUT generation incomplete - - グラデーションレンダリング - LUT生成が不完全 +5. **FrameBuffer**: Manages render targets and intermediate rendering buffers + - **フレームバッファ**: レンダーターゲットと中間レンダリングバッファを管理 -5. **Performance** - No optimization for buffer reuse, pipeline caching - - パフォーマンス - バッファ再利用、パイプラインキャッシングの最適化なし +6. **AtlasManager**: Optimizes texture usage through texture atlas packing + - **アトラスマネージャー**: テクスチャアトラスパッキングによりテクスチャ使用を最適化 -6. **Error Handling** - Limited validation and error recovery - - エラーハンドリング - 検証とエラー回復が制限的 +7. **Filter**: Applies post-processing effects (blur, glow, color matrix, etc.) + - **フィルター**: ポストプロセスエフェクト(ブラー、グロー、カラーマトリックスなど)を適用 ---- +8. **Blend**: Handles blend mode operations for compositing + - **ブレンド**: 合成のためのブレンドモード操作を処理 -## Development Notes / 開発ノート +9. **Mask**: Implements stencil-based masking for clipping and complex shapes + - **マスク**: クリッピングと複雑な形状のためのステンシルベースのマスキングを実装 -### Architecture / アーキテクチャ +## Atlas-Based Rendering Approach / アトラスベースのレンダリングアプローチ -The package follows a manager-based architecture similar to the WebGL implementation: +The WebGPU package uses a texture atlas system to optimize rendering performance: -パッケージは WebGL 実装と同様のマネージャーベースアーキテクチャに従います: +WebGPUパッケージは、レンダリングパフォーマンスを最適化するためにテクスチャアトラスシステムを使用しています: -- **Context**: Main rendering interface (メインレンダリングインターフェース) -- **Managers**: Resource lifecycle management (リソースライフサイクル管理) -- **Services/UseCases**: Business logic separation (ビジネスロジック分離) -- **Shaders**: WGSL source and pipeline configuration (WGSL ソースとパイプライン設定) +### Benefits / 利点 -### Frame Lifecycle / フレームライフサイクル +- **Reduced draw calls**: Multiple textures are packed into a single atlas, reducing the number of texture bindings + - **描画コールの削減**: 複数のテクスチャが単一のアトラスにパックされ、テクスチャバインディングの数が削減されます -``` -clearTransferBounds() - → beginFrame() - → [drawing operations] - → endFrame()/transferMainCanvas() -``` +- **Memory efficiency**: Optimal packing algorithm minimizes wasted GPU memory + - **メモリ効率**: 最適なパッキングアルゴリズムにより、無駄なGPUメモリが最小化されます -### Rendering Flow / レンダリングフロー +- **Cache coherency**: Better GPU cache utilization through spatial locality + - **キャッシュコヒーレンシー**: 空間的局所性による優れたGPUキャッシュ利用 -1. Acquire canvas texture (once per frame) (キャンバステクスチャ取得 - フレーム毎に1回) -2. Create command encoder (コマンドエンコーダー作成) -3. Begin render pass with load/clear (ロード/クリアでレンダーパス開始) -4. Set pipeline and bind resources (パイプライン設定とリソースバインド) -5. Draw commands (描画コマンド) -6. End render pass (レンダーパス終了) -7. Submit commands to queue (コマンドをキューに送信) +### Atlas Management / アトラス管理 ---- +The `AtlasManager` component uses the `@next2d/texture-packer` package to: -## Browser Compatibility / ブラウザ互換性 +`AtlasManager`コンポーネントは`@next2d/texture-packer`パッケージを使用して以下を実行します: -WebGPU support is required. As of 2024: +- Dynamically pack textures into optimal atlas layouts + - テクスチャを最適なアトラスレイアウトに動的にパック +- Track texture usage and automatically manage atlas allocation + - テクスチャ使用状況を追跡し、アトラス割り当てを自動管理 +- Support multiple atlas instances when texture requirements exceed single atlas capacity + - テクスチャ要件が単一のアトラス容量を超える場合、複数のアトラスインスタンスをサポート -WebGPU サポートが必要です。2024年時点: +### Rendering with Atlases / アトラスを使用したレンダリング -- ✅ Chrome/Edge 113+ -- ✅ Firefox 131+ (experimental) -- ✅ Safari 18+ (experimental) -- ❌ Older browsers (need WebGL fallback) +1. Textures are uploaded to GPU and registered with the atlas manager + - テクスチャがGPUにアップロードされ、アトラスマネージャーに登録されます ---- +2. The atlas manager assigns UV coordinates for each texture region + - アトラスマネージャーが各テクスチャ領域のUV座標を割り当てます -## Usage / 使用方法 +3. Shaders use these UV coordinates to sample from the correct atlas region + - シェーダーがこれらのUV座標を使用して正しいアトラス領域からサンプリングします -```typescript -import { Context } from "@next2d/webgpu"; +4. Multiple objects can be rendered in a single draw call using the same atlas + - 同じアトラスを使用して複数のオブジェクトを単一の描画コールでレンダリング可能 -// Get WebGPU adapter and device -const adapter = await navigator.gpu.requestAdapter(); -const device = await adapter.requestDevice(); +## Architecture Patterns / アーキテクチャパターン -// Get canvas context -const canvas = document.getElementById("canvas") as HTMLCanvasElement; -const context = canvas.getContext("webgpu") as GPUCanvasContext; +The codebase follows a clean architecture approach with clear separation of concerns: -// Get preferred format -const format = navigator.gpu.getPreferredCanvasFormat(); +コードベースは、関心事の明確な分離を伴うクリーンアーキテクチャアプローチに従っています: -// Create rendering context -const ctx = new Context(device, context, format); +### Service Layer / サービス層 -// Rendering -ctx.clearTransferBounds(); // Begin frame -ctx.fillStyle(1, 0, 0, 1); // Red -ctx.beginPath(); -ctx.arc(100, 100, 50); -ctx.fill(); -ctx.transferMainCanvas(); // End frame and submit -``` +Services contain low-level operations and business logic. They are pure functions that perform specific tasks. ---- +サービスは低レベルの操作とビジネスロジックを含みます。特定のタスクを実行する純粋な関数です。 -## Contributing / 貢献 +### Use Case Layer / ユースケース層 -As this package is work in progress, contributions are welcome! Priority areas: +Use cases orchestrate multiple services to accomplish higher-level operations. They represent application-specific business rules. -このパッケージは開発中のため、貢献を歓迎します!優先領域: +ユースケースは、より高レベルの操作を達成するために複数のサービスを調整します。アプリケーション固有のビジネスルールを表します。 -1. Completing gradient and bitmap fill/stroke shaders -2. Implementing filter parameter binding -3. Stencil-based masking operations -4. Performance optimization (buffer pooling, pipeline caching) -5. Comprehensive testing +## Dependencies / 依存関係 ---- +- `@next2d/texture-packer`: Texture atlas packing and management / テクスチャアトラスのパッキングと管理 +- `@next2d/render-queue`: Rendering operation queue management / レンダリング操作キュー管理 ## License / ライセンス -MIT License - -Copyright (c) 2021 Next2D - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---- - -## Related Packages / 関連パッケージ - -- `@next2d/texture-packer` - Texture atlas management -- `@next2d/render-queue` - Render queue for batch operations - ---- - -**Last Updated**: 2024-12-08 +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. -**Status**: 🚧 Experimental - Active Development +このプロジェクトは[MITライセンス](https://opensource.org/licenses/MIT)の下でライセンスされています - 詳細は[LICENSE](LICENSE)ファイルを参照してください。 diff --git a/specs/cn/events.md b/specs/cn/events.md index 526a02ec..926605d2 100644 --- a/specs/cn/events.md +++ b/specs/cn/events.md @@ -1,6 +1,6 @@ # 事件系统 -Next2D Player 使用与 Flash Player 类似的事件模型。 +Next2D Player 使用与 W3C DOM 事件模型类似的三阶段事件流机制。 ## EventDispatcher @@ -10,33 +10,53 @@ Next2D Player 使用与 Flash Player 类似的事件模型。 注册事件侦听器。 -```javascript -displayObject.addEventListener("click", function(event) { - console.log("被点击"); +```typescript +const { PointerEvent } = next2d.events; + +displayObject.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("指针被按下"); }); // 在捕获阶段接收 -displayObject.addEventListener("click", handler, true); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // 指定优先级 -displayObject.addEventListener("click", handler, false, 10); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, false, 10); ``` ### removeEventListener(type, listener, useCapture) 移除事件侦听器。 -```javascript -displayObject.removeEventListener("click", handler); +```typescript +displayObject.removeEventListener(PointerEvent.POINTER_DOWN, handler); +``` + +### removeAllEventListener(type, useCapture) + +移除特定类型的所有事件侦听器。 + +```typescript +displayObject.removeAllEventListener(PointerEvent.POINTER_DOWN); ``` ### hasEventListener(type) 检查是否注册了指定类型的侦听器。 -```javascript -if (displayObject.hasEventListener("click")) { - console.log("已注册点击侦听器"); +```typescript +if (displayObject.hasEventListener(PointerEvent.POINTER_DOWN)) { + console.log("已注册指针按下侦听器"); +} +``` + +### willTrigger(type) + +检查此对象或祖先是否具有该事件类型的侦听器。 + +```typescript +if (displayObject.willTrigger(PointerEvent.POINTER_DOWN)) { + console.log("此对象或祖先有侦听器"); } ``` @@ -44,7 +64,7 @@ if (displayObject.hasEventListener("click")) { 派发事件。 -```javascript +```typescript const { Event } = next2d.events; const event = new Event("customEvent"); @@ -62,7 +82,6 @@ displayObject.dispatchEvent(event); | `currentTarget` | Object | 当前侦听器目标 | | `eventPhase` | Number | 事件阶段 | | `bubbles` | Boolean | 是否冒泡 | -| `cancelable` | Boolean | 是否可取消 | ### 方法 @@ -70,7 +89,6 @@ displayObject.dispatchEvent(event); |------|------| | `stopPropagation()` | 停止传播 | | `stopImmediatePropagation()` | 立即停止传播 | -| `preventDefault()` | 取消默认行为 | ## 标准事件类型 @@ -83,8 +101,8 @@ displayObject.dispatchEvent(event); | `removed` | 从 DisplayObjectContainer 移除 | | `removedFromStage` | 从舞台移除 | -```javascript -sprite.addEventListener("addedToStage", function(event) { +```typescript +sprite.addEventListener("addedToStage", (event) => { console.log("添加到舞台"); }); ``` @@ -97,8 +115,8 @@ sprite.addEventListener("addedToStage", function(event) { | `frameConstructed` | 帧构建完成 | | `exitFrame` | 离开帧时 | -```javascript -movieClip.addEventListener("enterFrame", function(event) { +```typescript +movieClip.addEventListener("enterFrame", (event) => { // 每帧执行的处理 updatePosition(); }); @@ -111,59 +129,68 @@ movieClip.addEventListener("enterFrame", function(event) { | `complete` | 加载完成 | | `progress` | 加载进度 | | `ioError` | IO 错误 | +| `httpStatus` | HTTP 状态接收 | -```javascript +```typescript const { Loader } = next2d.display; const { URLRequest } = next2d.net; const loader = new Loader(); -loader.contentLoaderInfo.addEventListener("complete", function(event) { - const content = event.currentTarget.content; - stage.addChild(content); -}); +// 使用 async/await 加载 +await loader.load(new URLRequest("animation.json")); +const content = loader.content; +stage.addChild(content); -loader.contentLoaderInfo.addEventListener("progress", function(event) { +// 使用进度事件 +loader.contentLoaderInfo.addEventListener("progress", (event) => { const percent = (event.bytesLoaded / event.bytesTotal) * 100; - console.log(percent + "% 已加载"); + console.log(`${percent}% 已加载`); }); - -loader.load(new URLRequest("animation.json")); ``` -## 鼠标事件 +## 指针事件 -| 事件 | 说明 | -|------|------| -| `click` | 点击 | -| `doubleClick` | 双击 | -| `mouseDown` | 鼠标按下 | -| `mouseUp` | 鼠标释放 | -| `mouseMove` | 鼠标移动 | -| `mouseOver` | 鼠标移入 | -| `mouseOut` | 鼠标移出 | -| `rollOver` | 滚动移入 | -| `rollOut` | 滚动移出 | - -```javascript -sprite.addEventListener("click", function(event) { - console.log("点击位置:", event.localX, event.localY); +PointerEvent 统一处理指针设备的操作(鼠标、笔、触摸)。 + +| 事件 | 常量 | 说明 | +|------|------|------| +| `pointerDown` | `PointerEvent.POINTER_DOWN` | 按钮按下开始 | +| `pointerUp` | `PointerEvent.POINTER_UP` | 按钮释放 | +| `pointerMove` | `PointerEvent.POINTER_MOVE` | 指针坐标变化 | +| `pointerOver` | `PointerEvent.POINTER_OVER` | 指针进入命中测试边界 | +| `pointerOut` | `PointerEvent.POINTER_OUT` | 指针离开命中测试边界 | +| `pointerLeave` | `PointerEvent.POINTER_LEAVE` | 指针离开元素区域 | +| `pointerCancel` | `PointerEvent.POINTER_CANCEL` | 指针交互被取消 | +| `doubleClick` | `PointerEvent.DOUBLE_CLICK` | 双击/双触发生 | + +```typescript +const { PointerEvent } = next2d.events; + +sprite.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("指针按下:", event.localX, event.localY); }); -sprite.addEventListener("mouseMove", function(event) { - console.log("鼠标位置:", event.stageX, event.stageY); +sprite.addEventListener(PointerEvent.POINTER_MOVE, (event) => { + console.log("指针移动:", event.stageX, event.stageY); +}); + +sprite.addEventListener(PointerEvent.DOUBLE_CLICK, (event) => { + console.log("双击"); }); ``` ## 键盘事件 -| 事件 | 说明 | -|------|------| -| `keyDown` | 按键按下 | -| `keyUp` | 按键释放 | +| 事件 | 常量 | 说明 | +|------|------|------| +| `keyDown` | `KeyboardEvent.KEY_DOWN` | 按键按下 | +| `keyUp` | `KeyboardEvent.KEY_UP` | 按键释放 | + +```typescript +const { KeyboardEvent } = next2d.events; -```javascript -stage.addEventListener("keyDown", function(event) { +stage.addEventListener(KeyboardEvent.KEY_DOWN, (event) => { console.log("键码:", event.keyCode); switch (event.keyCode) { @@ -177,9 +204,56 @@ stage.addEventListener("keyDown", function(event) { }); ``` +## 焦点事件 + +| 事件 | 常量 | 说明 | +|------|------|------| +| `focusIn` | `FocusEvent.FOCUS_IN` | 元素获得焦点 | +| `focusOut` | `FocusEvent.FOCUS_OUT` | 元素失去焦点 | + +```typescript +const { FocusEvent } = next2d.events; + +textField.addEventListener(FocusEvent.FOCUS_IN, (event) => { + console.log("获得焦点"); +}); +``` + +## 滚轮事件 + +| 事件 | 常量 | 说明 | +|------|------|------| +| `wheel` | `WheelEvent.WHEEL` | 鼠标滚轮旋转 | + +```typescript +const { WheelEvent } = next2d.events; + +stage.addEventListener(WheelEvent.WHEEL, (event) => { + console.log("滚轮旋转"); +}); +``` + +## 视频事件 + +| 事件 | 常量 | 说明 | +|------|------|------| +| `play` | `VideoEvent.PLAY` | 播放被请求 | +| `playing` | `VideoEvent.PLAYING` | 播放已开始 | +| `pause` | `VideoEvent.PAUSE` | 已暂停 | +| `seek` | `VideoEvent.SEEK` | 跳转操作 | + +## 作业事件 + +补间动画用的事件。 + +| 事件 | 常量 | 说明 | +|------|------|------| +| `update` | `JobEvent.UPDATE` | 属性已更新 | +| `stop` | `JobEvent.STOP` | 作业已停止 | + ## 自定义事件 -```javascript +```typescript const { Event } = next2d.events; // 定义自定义事件 @@ -189,7 +263,7 @@ const customEvent = new Event("gameOver", true, true); gameManager.dispatchEvent(customEvent); // 监听事件 -gameManager.addEventListener("gameOver", function(event) { +gameManager.addEventListener("gameOver", (event) => { showGameOverScreen(); }); ``` @@ -198,16 +272,18 @@ gameManager.addEventListener("gameOver", function(event) { 事件分三个阶段传播: -1. **捕获阶段**:从根到目标 -2. **目标阶段**:在目标处处理 -3. **冒泡阶段**:从目标到根 +1. **捕获阶段**:从根到目标(eventPhase = 1) +2. **目标阶段**:在目标处处理(eventPhase = 2) +3. **冒泡阶段**:从目标到根(eventPhase = 3) + +```typescript +const { PointerEvent } = next2d.events; -```javascript // 在捕获阶段处理 -parent.addEventListener("click", handler, true); +parent.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // 在冒泡阶段处理(默认) -child.addEventListener("click", handler, false); +child.addEventListener(PointerEvent.POINTER_DOWN, handler, false); ``` ## 相关 diff --git a/specs/cn/movie-clip.md b/specs/cn/movie-clip.md index 29fa6f9f..880f09c0 100644 --- a/specs/cn/movie-clip.md +++ b/specs/cn/movie-clip.md @@ -114,8 +114,9 @@ movieClip.addEventListener("exitFrame", function(event) { ### 基本动画控制 -```javascript +```typescript const { Loader } = next2d.display; +const { PointerEvent } = next2d.events; const { URLRequest } = next2d.net; // 从 JSON 加载 MovieClip @@ -129,7 +130,7 @@ stage.addChild(mc); mc.stop(); // 点击按钮播放/暂停 -button.addEventListener("click", function() { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { if (mc.isPlaying) { mc.stop(); } else { diff --git a/specs/cn/shape.md b/specs/cn/shape.md index a666ef8b..f9150f79 100644 --- a/specs/cn/shape.md +++ b/specs/cn/shape.md @@ -358,19 +358,18 @@ stage.addChild(shape); #### 位图填充 -```javascript -const { Shape, Loader } = next2d.display; - -const loader = new Loader(); -await loader.load("texture.png"); +```typescript +const { Shape } = next2d.display; -const bitmapData = loader.contentLoaderInfo - .content.bitmapData; +// 使用 Shape 的 load() 方法加载图像 +const textureShape = new Shape(); +await textureShape.load("texture.png"); +// 使用加载的 bitmapData 进行位图填充 const shape = new Shape(); const g = shape.graphics; -g.beginBitmapFill(bitmapData, null, true, true); +g.beginBitmapFill(textureShape.bitmapData, null, true, true); g.drawRect(0, 0, 400, 300); g.endFill(); diff --git a/specs/cn/sprite.md b/specs/cn/sprite.md index 070112a9..c3154bb6 100644 --- a/specs/cn/sprite.md +++ b/specs/cn/sprite.md @@ -124,8 +124,9 @@ classDiagram ### 作为按钮使用 -```javascript +```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const button = new Sprite(); @@ -141,7 +142,7 @@ bg.graphics.endFill(); button.addChild(bg); // 点击事件 -button.addEventListener("click", function() { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { console.log("按钮被点击"); }); @@ -177,8 +178,9 @@ stage.addChild(maskShape); ### 拖放 -```javascript +```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const { Rectangle } = next2d.geom; const draggable = new Sprite(); @@ -191,13 +193,13 @@ bg.graphics.endFill(); draggable.addChild(bg); // 开始拖动 -draggable.addEventListener("mouseDown", function() { +draggable.addEventListener(PointerEvent.POINTER_DOWN, () => { // 开始拖动(锁定中心,指定边界) draggable.startDrag(true, new Rectangle(0, 0, 400, 300)); }); // 停止拖动 -draggable.addEventListener("mouseUp", function() { +draggable.addEventListener(PointerEvent.POINTER_UP, () => { draggable.stopDrag(); }); diff --git a/specs/cn/tween.md b/specs/cn/tween.md index 57c5ec2f..5717bb64 100644 --- a/specs/cn/tween.md +++ b/specs/cn/tween.md @@ -1,6 +1,6 @@ # 补间动画 -Next2D Player 允许您实现程序化动画(补间)。您可以平滑地动画化位置、大小和透明度等属性。 +Next2D Player 使用 `@next2d/ui` 包的 Tween 系统来实现程序化动画。您可以平滑地动画化位置、大小和透明度等属性。 ## 基本补间概念 @@ -10,364 +10,365 @@ flowchart LR Progress --> End["结束值"] subgraph Easing["缓动"] - Linear["线性"] - EaseIn["EaseIn"] - EaseOut["EaseOut"] - EaseInOut["EaseInOut"] + Linear["linear"] + InQuad["inQuad"] + OutQuad["outQuad"] + InOutQuad["inOutQuad"] end ``` -## 基本 Tween 类 - -```javascript -class Tween { - constructor(target, options) { - this._target = target; - this._properties = {}; - this._duration = options.duration; - this._easing = options.easing || Easing.linear; - this._startTime = 0; - this._isPlaying = false; - this._onUpdate = options.onUpdate; - this._onComplete = options.onComplete; - } - - to(properties) { - for (const key in properties) { - this._properties[key] = { - start: this._target[key], - end: properties[key] - }; - } - return this; - } - - play() { - this._startTime = Date.now(); - this._isPlaying = true; - this._update(); - return this; - } - - _update() { - const self = this; - if (!this._isPlaying) return; - - const elapsed = Date.now() - this._startTime; - let progress = Math.min(1, elapsed / this._duration); - progress = this._easing(progress); - - // 更新属性 - for (const key in this._properties) { - const prop = this._properties[key]; - this._target[key] = prop.start + (prop.end - prop.start) * progress; - } - - if (this._onUpdate) { - this._onUpdate(); - } - - if (elapsed < this._duration) { - requestAnimationFrame(function() { self._update(); }); - } else { - this._isPlaying = false; - if (this._onComplete) { - this._onComplete(); - } - } - } - - stop() { - this._isPlaying = false; - } -} +## Tween.add() + +使用 `Tween.add()` 方法创建动画用的 `Job` 实例。 + +```typescript +const { Tween, Easing } = next2d.ui; + +const job = Tween.add( + target, // 动画目标对象 + from, // 起始属性值 + to, // 结束属性值 + delay, // 延迟时间(秒,默认:0) + duration, // 动画持续时间(秒,默认:1) + ease // 缓动函数(默认:linear) +); + +// 开始动画 +job.start(); ``` +### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `target` | any | - | 动画目标对象 | +| `from` | object | - | 起始属性值 | +| `to` | object | - | 结束属性值 | +| `delay` | number | 0 | 动画开始前的延迟(秒) | +| `duration` | number | 1 | 动画持续时间(秒) | +| `ease` | Function \| null | null | 缓动函数(默认为 linear) | + +### 返回值 + +`Job` - 动画作业实例 + +## Job 类 + +Job 类管理各个动画作业。它继承自 EventDispatcher。 + +### 方法 + +| 方法 | 返回值 | 说明 | +|------|--------|------| +| `start()` | void | 开始动画 | +| `stop()` | void | 停止动画 | +| `chain(nextJob: Job \| null)` | Job \| null | 在此作业完成后连接另一个作业 | + +### 属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `target` | any | 目标对象 | +| `from` | object | 起始值 | +| `to` | object | 结束值 | +| `delay` | number | 延迟时间 | +| `duration` | number | 持续时间 | +| `ease` | Function | 缓动函数 | +| `currentTime` | number | 当前动画时间 | +| `nextJob` | Job \| null | 下一个连接的作业 | + +### 事件 + +| 事件 | 说明 | +|------|------| +| `enterFrame` | 每个动画帧触发 | +| `complete` | 动画完成时触发 | + ## 缓动函数 -```javascript -const Easing = { - // 线性 - linear: function(t) { return t; }, - - // 加速 - easeInQuad: function(t) { return t * t; }, - easeInCubic: function(t) { return t * t * t; }, - easeInQuart: function(t) { return t * t * t * t; }, - - // 减速 - easeOutQuad: function(t) { return t * (2 - t); }, - easeOutCubic: function(t) { return (--t) * t * t + 1; }, - easeOutQuart: function(t) { return 1 - (--t) * t * t * t; }, - - // 加速 → 减速 - easeInOutQuad: function(t) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; - }, - easeInOutCubic: function(t) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; - }, - - // 弹跳 - easeOutBounce: function(t) { - if (t < 1 / 2.75) { - return 7.5625 * t * t; - } else if (t < 2 / 2.75) { - return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75; - } else if (t < 2.5 / 2.75) { - return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375; - } else { - return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375; - } - }, - - // Back(超调然后返回) - easeOutBack: function(t) { - const c1 = 1.70158; - const c3 = c1 + 1; - return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); - }, - - // 弹性(橡皮筋般的运动) - easeOutElastic: function(t) { - if (t === 0 || t === 1) return t; - return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI) / 3) + 1; - } -}; +`Easing` 类提供 11 种缓动类型的 In、Out、InOut 变体,共 32 种缓动函数。 + +### Linear / 线性 +- `Easing.linear` - 匀速 + +### Quadratic (Quad) / 二次函数 +- `Easing.inQuad` - 从零速度加速 +- `Easing.outQuad` - 减速到零速度 +- `Easing.inOutQuad` - 加速到中间,然后减速 + +### Cubic / 三次函数 +- `Easing.inCubic` / `Easing.outCubic` / `Easing.inOutCubic` + +### Quartic (Quart) / 四次函数 +- `Easing.inQuart` / `Easing.outQuart` / `Easing.inOutQuart` + +### Quintic (Quint) / 五次函数 +- `Easing.inQuint` / `Easing.outQuint` / `Easing.inOutQuint` + +### Sinusoidal (Sine) / 正弦波 +- `Easing.inSine` / `Easing.outSine` / `Easing.inOutSine` + +### Exponential (Expo) / 指数函数 +- `Easing.inExpo` / `Easing.outExpo` / `Easing.inOutExpo` + +### Circular (Circ) / 圆形 +- `Easing.inCirc` / `Easing.outCirc` / `Easing.inOutCirc` + +### Elastic / 弹性 +- `Easing.inElastic` / `Easing.outElastic` / `Easing.inOutElastic` + +### Back +- `Easing.inBack` / `Easing.outBack` / `Easing.inOutBack` + +### Bounce / 弹跳 +- `Easing.inBounce` / `Easing.outBounce` / `Easing.inOutBounce` + +### 缓动函数参数 + +所有缓动函数接受 4 个参数: + +```typescript +ease(t: number, b: number, c: number, d: number): number ``` +- `t` - 当前时间 (0 到 d) +- `b` - 起始值 +- `c` - 变化量(结束值 - 起始值) +- `d` - 持续时间 + ## 使用示例 ### 基本移动动画 -```javascript -const { Sprite } = next2d.display; +```typescript +const { Tween, Easing } = next2d.ui; const sprite = new Sprite(); -sprite.x = 0; -sprite.y = 100; stage.addChild(sprite); -// 向右移动 -new Tween(sprite, { duration: 1000, easing: Easing.easeOutQuad }) - .to({ x: 400 }) - .play(); +// 在 1 秒内将 x 从 0 移动到 400 +const job = Tween.add( + sprite, + { x: 0, y: 100 }, + { x: 400, y: 100 }, + 0, + 1, + Easing.outQuad +); + +job.start(); ``` ### 同时多属性动画 -```javascript +```typescript +const { Tween, Easing } = next2d.ui; + // 移动 + 缩放 + 淡入 -new Tween(sprite, { - duration: 500, - easing: Easing.easeOutCubic -}) - .to({ - x: 200, - y: 150, - scaleX: 2, - scaleY: 2, - alpha: 1 - }) - .play(); +const job = Tween.add( + sprite, + { x: 0, y: 0, scaleX: 1, scaleY: 1, alpha: 0 }, + { x: 200, y: 150, scaleX: 2, scaleY: 2, alpha: 1 }, + 0, + 0.5, + Easing.outCubic +); + +job.start(); ``` -### 顺序动画 - -```javascript -// 连续动画 -function sequentialAnimation(sprite) { - new Tween(sprite, { - duration: 500, - onComplete: function() { - new Tween(sprite, { - duration: 300, - onComplete: function() { - new Tween(sprite, { duration: 500 }) - .to({ alpha: 0 }) - .play(); - } - }) - .to({ scaleX: 1.5, scaleY: 1.5 }) - .play(); - } - }) - .to({ y: 100 }) - .play(); -} +### 动画连接 (chain) + +```typescript +const { Tween, Easing } = next2d.ui; + +// 第一个动画 +const job1 = Tween.add( + sprite, + { x: 0 }, + { x: 100 }, + 0, 1, + Easing.outQuad +); + +// 第二个动画 +const job2 = Tween.add( + sprite, + { x: 100 }, + { x: 200 }, + 0, 1, + Easing.inQuad +); + +// 连接并执行 +job1.chain(job2); +job1.start(); ``` -### 游戏示例 +### 延迟动画 -#### 角色跳跃 +```typescript +const { Tween, Easing } = next2d.ui; -```javascript -function jump(character) { - const startY = character.y; - const jumpHeight = 100; +// 延迟 0.5 秒后在 1 秒内淡出 +const job = Tween.add( + sprite, + { alpha: 1 }, + { alpha: 0 }, + 0.5, + 1, + Easing.inQuad +); - // 上升 - new Tween(character, { - duration: 300, - easing: Easing.easeOutQuad, - onComplete: function() { - // 下降 - new Tween(character, { - duration: 300, - easing: Easing.easeInQuad - }) - .to({ y: startY }) - .play(); - } - }) - .to({ y: startY - jumpHeight }) - .play(); -} +job.start(); ``` -#### 伤害效果 +### 使用事件 -```javascript -function damageEffect(target) { - const originalX = target.x; - let shakeCount = 0; +```typescript +const { Tween, Easing } = next2d.ui; - // 闪烁 + 震动 - function shake() { - if (shakeCount >= 6) { - target.x = originalX; - target.alpha = 1; - return; - } +const job = Tween.add( + sprite, + { x: 0 }, + { x: 300 }, + 0, 2, + Easing.inOutQuad +); - const offset = shakeCount % 2 === 0 ? 5 : -5; - target.x = originalX + offset; - target.alpha = shakeCount % 2 === 0 ? 0.5 : 1; - shakeCount++; +// 每帧处理 +job.addEventListener("enterFrame", (event) => { + console.log("进行中:", job.currentTime); +}); - setTimeout(shake, 50); - } +// 完成时处理 +job.addEventListener("complete", (event) => { + console.log("动画完成!"); +}); - shake(); -} +job.start(); ``` -#### 金币收集效果 +### 游戏示例 -```javascript -function coinCollectEffect(coin, targetY) { - // 向上浮动并淡出 - new Tween(coin, { - duration: 500, - easing: Easing.easeOutQuad, - onUpdate: function() { - // 旋转 - coin.rotation += 15; - }, - onComplete: function() { - if (coin.parent) { - coin.parent.removeChild(coin); - } - } - }) - .to({ - y: targetY, - alpha: 0, - scaleX: 0.5, - scaleY: 0.5 - }) - .play(); +#### 角色跳跃 + +```typescript +const { Tween, Easing } = next2d.ui; + +function jump(character) { + const startY = character.y; + const jumpHeight = 100; + + // 上升 + const upJob = Tween.add( + character, + { y: startY }, + { y: startY - jumpHeight }, + 0, 0.3, + Easing.outQuad + ); + + // 下降 + const downJob = Tween.add( + character, + { y: startY - jumpHeight }, + { y: startY }, + 0, 0.3, + Easing.inQuad + ); + + // 连接上升 → 下降 + upJob.chain(downJob); + upJob.start(); } ``` #### UI 动画 -```javascript +```typescript +const { Tween, Easing } = next2d.ui; + function showPopup(popup) { popup.scaleX = 0; popup.scaleY = 0; popup.alpha = 0; - new Tween(popup, { - duration: 400, - easing: Easing.easeOutBack - }) - .to({ scaleX: 1, scaleY: 1, alpha: 1 }) - .play(); + const job = Tween.add( + popup, + { scaleX: 0, scaleY: 0, alpha: 0 }, + { scaleX: 1, scaleY: 1, alpha: 1 }, + 0, 0.4, + Easing.outBack + ); + + job.start(); } -function hidePopup(popup, onComplete) { - new Tween(popup, { - duration: 200, - easing: Easing.easeInQuad, - onComplete: onComplete - }) - .to({ scaleX: 0, scaleY: 0, alpha: 0 }) - .play(); +function hidePopup(popup) { + const job = Tween.add( + popup, + { scaleX: 1, scaleY: 1, alpha: 1 }, + { scaleX: 0, scaleY: 0, alpha: 0 }, + 0, 0.2, + Easing.inQuad + ); + + job.addEventListener("complete", () => { + popup.visible = false; + }); + + job.start(); } ``` -## 基于 enterFrame 的轻量级补间 +#### 金币收集效果 -```javascript -// 简单的基于 enterFrame 的补间 -function tweenTo(target, property, endValue, speed) { - speed = speed || 0.1; +```typescript +const { Tween, Easing } = next2d.ui; - function handler(event) { - const current = target[property]; - const diff = endValue - current; +function coinCollectEffect(coin) { + const job = Tween.add( + coin, + { y: coin.y, alpha: 1, scaleX: 1, scaleY: 1 }, + { y: coin.y - 50, alpha: 0, scaleX: 0.5, scaleY: 0.5 }, + 0, 0.5, + Easing.outQuad + ); - if (Math.abs(diff) < 0.1) { - target[property] = endValue; - stage.removeEventListener("enterFrame", handler); - } else { - target[property] = current + diff * speed; - } - } + job.addEventListener("enterFrame", () => { + coin.rotation += 15; + }); - stage.addEventListener("enterFrame", handler); -} + job.addEventListener("complete", () => { + coin.parent?.removeChild(coin); + }); -// 使用 -tweenTo(sprite, "x", 300, 0.15); // 将 x 移向 300 -tweenTo(sprite, "alpha", 0, 0.05); // 淡出 + job.start(); +} ``` -## 自定义缓动 +### 停止和控制 -```javascript -// 基于贝塞尔曲线的缓动 -function bezierEasing(x1, y1, x2, y2) { - return function(t) { - // 简单的三次贝塞尔插值 - const cx = 3 * x1; - const bx = 3 * (x2 - x1) - cx; - const ax = 1 - cx - bx; +```typescript +const { Tween, Easing } = next2d.ui; - const cy = 3 * y1; - const by = 3 * (y2 - y1) - cy; - const ay = 1 - cy - by; +const job = Tween.add( + sprite, + { x: 0 }, + { x: 400 }, + 0, 2, + Easing.linear +); - function sampleCurveY(t) { - return ((ay * t + by) * t + cy) * t; - } +job.start(); - return sampleCurveY(t); - }; -} - -// CSS cubic-bezier 等效 -const customEase = bezierEasing(0.25, 0.1, 0.25, 1.0); +// 中途停止 +stopButton.addEventListener(PointerEvent.POINTER_DOWN, () => { + job.stop(); +}); ``` -## 性能提示 - -1. **使用 requestAnimationFrame**:比 setTimeout 更流畅 -2. **最小化属性更改**:只更新必要的属性 -3. **对象池**:为多个动画池化和重用补间 -4. **完成后清理**:移除不必要的侦听器 - ## 相关 - [DisplayObject](/cn/reference/player/display-object) diff --git a/specs/cn/video.md b/specs/cn/video.md index 4cbca489..7322916c 100644 --- a/specs/cn/video.md +++ b/specs/cn/video.md @@ -39,7 +39,7 @@ classDiagram | `duration` | number | 0 | 总关键帧数(视频持续时间) | | `currentTime` | number | 0 | 当前关键帧(播放位置) | | `volume` | number | 1 | 音量,范围从 0(静音)到 1(最大音量) | -| `loop` | boolean | false | 指定是否生成视频循环 | +| `loop` | boolean | false | 指定是否循环播放视频 | | `autoPlay` | boolean | true | 设置自动视频播放 | | `smoothing` | boolean | true | 指定缩放时是否对视频进行平滑(插值) | | `paused` | boolean | true | 返回视频是否已暂停 | @@ -60,17 +60,19 @@ classDiagram ### 基本视频播放 -```javascript +```typescript const { Video } = next2d.media; -// 创建 Video 对象 +// 创建 Video 对象(指定宽度、高度) const video = new Video(640, 360); -// 设置视频源 +// 设置视频源(设置后自动开始加载) video.src = "video.mp4"; -video.autoPlay = true; -video.loop = false; -video.volume = 0.8; + +// 属性设置 +video.autoPlay = true; // 自动播放 +video.loop = false; // 不循环 +video.smoothing = true; // 启用平滑 // 添加到舞台 stage.addChild(video); @@ -78,43 +80,78 @@ stage.addChild(video); ### 播放控制 -```javascript +```typescript const { Video } = next2d.media; +const { PointerEvent } = next2d.events; const video = new Video(640, 360); +video.autoPlay = false; // 禁用自动播放 video.src = "video.mp4"; + stage.addChild(video); // 播放按钮 -playButton.addEventListener("click", async function() { +playButton.addEventListener(PointerEvent.POINTER_DOWN, async () => { await video.play(); }); // 暂停按钮 -pauseButton.addEventListener("click", function() { +pauseButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); }); // 停止按钮(暂停并返回开始) -stopButton.addEventListener("click", function() { +stopButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); video.seek(0); }); // 快进 10 秒 -forwardButton.addEventListener("click", function() { +forwardButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(video.currentTime + 10); }); // 后退 10 秒 -backButton.addEventListener("click", function() { +backButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(Math.max(0, video.currentTime - 10)); }); ``` +### 事件侦听 + +```typescript +const { Video } = next2d.media; +const { VideoEvent } = next2d.events; + +const video = new Video(640, 360); + +// 播放事件 +video.addEventListener(VideoEvent.PLAY, () => { + console.log("播放请求"); +}); + +// 开始播放事件 +video.addEventListener(VideoEvent.PLAYING, () => { + console.log("播放开始"); +}); + +// 暂停事件 +video.addEventListener(VideoEvent.PAUSE, () => { + console.log("已暂停"); +}); + +// 跳转事件 +video.addEventListener(VideoEvent.SEEK, () => { + console.log("跳转:", video.currentTime); +}); + +video.src = "video.mp4"; +stage.addChild(video); +``` + ### 显示播放进度 -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); @@ -122,7 +159,7 @@ video.src = "video.mp4"; stage.addChild(video); // 每帧更新进度 -stage.addEventListener("enterFrame", function() { +stage.addEventListener("enterFrame", () => { if (video.duration > 0) { const progress = video.currentTime / video.duration; progressBar.scaleX = progress; @@ -139,129 +176,41 @@ function formatTime(seconds) { ### 音量控制 -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); video.src = "video.mp4"; video.volume = 0.5; // 50% -stage.addChild(video); -// 音量滑块 -volumeSlider.addEventListener("change", function(event) { - video.volume = event.target.value; // 0.0 ~ 1.0 -}); +stage.addChild(video); // 静音切换 -let isMuted = false; -let previousVolume = 0.5; - -muteButton.addEventListener("click", function() { - isMuted = !isMuted; - if (isMuted) { - previousVolume = video.volume; - video.volume = 0; - } else { - video.volume = previousVolume; - } +muteButton.addEventListener(PointerEvent.POINTER_DOWN, () => { + video.muted = !video.muted; }); ``` -### 全屏支持 +### 循环播放 -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); +video.loop = true; // 启用循环 video.src = "video.mp4"; -stage.addChild(video); -// 全屏切换 -fullscreenButton.addEventListener("click", function() { - if (stage.displayState === "normal") { - // 切换到全屏 - stage.displayState = "fullScreen"; - video.width = stage.stageWidth; - video.height = stage.stageHeight; - } else { - // 返回正常显示 - stage.displayState = "normal"; - video.width = 640; - video.height = 360; - } -}); -``` - -### 视频播放器组件 - -```javascript -const { Sprite } = next2d.display; -const { Video } = next2d.media; - -class VideoPlayer extends Sprite { - constructor(width, height) { - super(); - - this._width = width; - this._height = height; - - this._video = new Video(width, height); - this.addChild(this._video); - } - - load(url) { - this._video.src = url; - } - - async play() { - await this._video.play(); - } - - pause() { - this._video.pause(); - } - - seek(time) { - this._video.seek(time); - } - - get currentTime() { - return this._video.currentTime; - } - - get duration() { - return this._video.duration || 0; - } - - set volume(value) { - this._video.volume = value; - } - - get volume() { - return this._video.volume; - } -} - -// 使用 -const player = new VideoPlayer(640, 360); -stage.addChild(player); -player.load("video.mp4"); -player.play(); +stage.addChild(video); ``` -### 循环播放和自动播放 - -```javascript -const { Video } = next2d.media; +## VideoEvent -const video = new Video(640, 360); -video.src = "background-video.mp4"; -video.autoPlay = true; -video.loop = true; -video.volume = 0; // 静音背景视频 - -stage.addChild(video); -``` +| 事件 | 说明 | +|------|------| +| `VideoEvent.PLAY` | 播放被请求时 | +| `VideoEvent.PLAYING` | 播放开始时 | +| `VideoEvent.PAUSE` | 暂停时 | +| `VideoEvent.SEEK` | 跳转时 | ## 支持的格式 diff --git a/specs/en/events.md b/specs/en/events.md index 06fcc9a7..5d27bac3 100644 --- a/specs/en/events.md +++ b/specs/en/events.md @@ -1,6 +1,6 @@ # Event System -Next2D Player uses an event model similar to Flash Player. +Next2D Player uses a three-phase event flow mechanism similar to the W3C DOM event model. ## EventDispatcher @@ -10,33 +10,53 @@ The base class for all event-capable objects. Registers an event listener. -```javascript -displayObject.addEventListener("click", function(event) { - console.log("Clicked"); +```typescript +const { PointerEvent } = next2d.events; + +displayObject.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("Pointer pressed"); }); // Receive in capture phase -displayObject.addEventListener("click", handler, true); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // Specify priority -displayObject.addEventListener("click", handler, false, 10); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, false, 10); ``` ### removeEventListener(type, listener, useCapture) Removes an event listener. -```javascript -displayObject.removeEventListener("click", handler); +```typescript +displayObject.removeEventListener(PointerEvent.POINTER_DOWN, handler); +``` + +### removeAllEventListener(type, useCapture) + +Removes all event listeners of a specific type. + +```typescript +displayObject.removeAllEventListener(PointerEvent.POINTER_DOWN); ``` ### hasEventListener(type) Checks if a listener of the specified type is registered. -```javascript -if (displayObject.hasEventListener("click")) { - console.log("Click listener is registered"); +```typescript +if (displayObject.hasEventListener(PointerEvent.POINTER_DOWN)) { + console.log("Pointer down listener is registered"); +} +``` + +### willTrigger(type) + +Checks if this object or any ancestor has a listener for the event type. + +```typescript +if (displayObject.willTrigger(PointerEvent.POINTER_DOWN)) { + console.log("This object or an ancestor has a listener"); } ``` @@ -44,7 +64,7 @@ if (displayObject.hasEventListener("click")) { Dispatches an event. -```javascript +```typescript const { Event } = next2d.events; const event = new Event("customEvent"); @@ -62,7 +82,6 @@ displayObject.dispatchEvent(event); | `currentTarget` | Object | Current listener target | | `eventPhase` | Number | Event phase | | `bubbles` | Boolean | Whether bubbles | -| `cancelable` | Boolean | Whether cancelable | ### Methods @@ -70,7 +89,6 @@ displayObject.dispatchEvent(event); |--------|-------------| | `stopPropagation()` | Stop propagation | | `stopImmediatePropagation()` | Stop propagation immediately | -| `preventDefault()` | Cancel default behavior | ## Standard Event Types @@ -83,8 +101,8 @@ displayObject.dispatchEvent(event); | `removed` | Removed from DisplayObjectContainer | | `removedFromStage` | Removed from Stage | -```javascript -sprite.addEventListener("addedToStage", function(event) { +```typescript +sprite.addEventListener("addedToStage", (event) => { console.log("Added to stage"); }); ``` @@ -97,8 +115,8 @@ sprite.addEventListener("addedToStage", function(event) { | `frameConstructed` | Frame construction complete | | `exitFrame` | When leaving frame | -```javascript -movieClip.addEventListener("enterFrame", function(event) { +```typescript +movieClip.addEventListener("enterFrame", (event) => { // Processing executed every frame updatePosition(); }); @@ -111,59 +129,68 @@ movieClip.addEventListener("enterFrame", function(event) { | `complete` | Load complete | | `progress` | Load progress | | `ioError` | IO error | +| `httpStatus` | HTTP status received | -```javascript +```typescript const { Loader } = next2d.display; const { URLRequest } = next2d.net; const loader = new Loader(); -loader.contentLoaderInfo.addEventListener("complete", function(event) { - const content = event.currentTarget.content; - stage.addChild(content); -}); +// Loading with async/await +await loader.load(new URLRequest("animation.json")); +const content = loader.content; +stage.addChild(content); -loader.contentLoaderInfo.addEventListener("progress", function(event) { +// Using progress events +loader.contentLoaderInfo.addEventListener("progress", (event) => { const percent = (event.bytesLoaded / event.bytesTotal) * 100; - console.log(percent + "% loaded"); + console.log(`${percent}% loaded`); }); - -loader.load(new URLRequest("animation.json")); ``` -## Mouse Events +## Pointer Events -| Event | Description | -|-------|-------------| -| `click` | Click | -| `doubleClick` | Double click | -| `mouseDown` | Mouse button pressed | -| `mouseUp` | Mouse button released | -| `mouseMove` | Mouse move | -| `mouseOver` | Mouse over | -| `mouseOut` | Mouse out | -| `rollOver` | Roll over | -| `rollOut` | Roll out | - -```javascript -sprite.addEventListener("click", function(event) { - console.log("Click position:", event.localX, event.localY); +PointerEvent handles pointer device interactions (mouse, pen, touch) in a unified way. + +| Event | Constant | Description | +|-------|----------|-------------| +| `pointerDown` | `PointerEvent.POINTER_DOWN` | Button press started | +| `pointerUp` | `PointerEvent.POINTER_UP` | Button released | +| `pointerMove` | `PointerEvent.POINTER_MOVE` | Pointer coordinates changed | +| `pointerOver` | `PointerEvent.POINTER_OVER` | Pointer entered hit test boundary | +| `pointerOut` | `PointerEvent.POINTER_OUT` | Pointer left hit test boundary | +| `pointerLeave` | `PointerEvent.POINTER_LEAVE` | Pointer left element area | +| `pointerCancel` | `PointerEvent.POINTER_CANCEL` | Pointer interaction canceled | +| `doubleClick` | `PointerEvent.DOUBLE_CLICK` | Double-click/tap occurred | + +```typescript +const { PointerEvent } = next2d.events; + +sprite.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("Pointer down:", event.localX, event.localY); }); -sprite.addEventListener("mouseMove", function(event) { - console.log("Mouse position:", event.stageX, event.stageY); +sprite.addEventListener(PointerEvent.POINTER_MOVE, (event) => { + console.log("Pointer move:", event.stageX, event.stageY); +}); + +sprite.addEventListener(PointerEvent.DOUBLE_CLICK, (event) => { + console.log("Double click"); }); ``` ## Keyboard Events -| Event | Description | -|-------|-------------| -| `keyDown` | Key pressed | -| `keyUp` | Key released | +| Event | Constant | Description | +|-------|----------|-------------| +| `keyDown` | `KeyboardEvent.KEY_DOWN` | Key pressed | +| `keyUp` | `KeyboardEvent.KEY_UP` | Key released | + +```typescript +const { KeyboardEvent } = next2d.events; -```javascript -stage.addEventListener("keyDown", function(event) { +stage.addEventListener(KeyboardEvent.KEY_DOWN, (event) => { console.log("Key code:", event.keyCode); switch (event.keyCode) { @@ -177,9 +204,56 @@ stage.addEventListener("keyDown", function(event) { }); ``` +## Focus Events + +| Event | Constant | Description | +|-------|----------|-------------| +| `focusIn` | `FocusEvent.FOCUS_IN` | Element received focus | +| `focusOut` | `FocusEvent.FOCUS_OUT` | Element lost focus | + +```typescript +const { FocusEvent } = next2d.events; + +textField.addEventListener(FocusEvent.FOCUS_IN, (event) => { + console.log("Received focus"); +}); +``` + +## Wheel Events + +| Event | Constant | Description | +|-------|----------|-------------| +| `wheel` | `WheelEvent.WHEEL` | Mouse wheel rotated | + +```typescript +const { WheelEvent } = next2d.events; + +stage.addEventListener(WheelEvent.WHEEL, (event) => { + console.log("Wheel rotated"); +}); +``` + +## Video Events + +| Event | Constant | Description | +|-------|----------|-------------| +| `play` | `VideoEvent.PLAY` | Play requested | +| `playing` | `VideoEvent.PLAYING` | Playback started | +| `pause` | `VideoEvent.PAUSE` | Paused | +| `seek` | `VideoEvent.SEEK` | Seek operation | + +## Job Events + +Events for Tween animations. + +| Event | Constant | Description | +|-------|----------|-------------| +| `update` | `JobEvent.UPDATE` | Property updated | +| `stop` | `JobEvent.STOP` | Job stopped | + ## Custom Events -```javascript +```typescript const { Event } = next2d.events; // Define custom event @@ -189,7 +263,7 @@ const customEvent = new Event("gameOver", true, true); gameManager.dispatchEvent(customEvent); // Listen to event -gameManager.addEventListener("gameOver", function(event) { +gameManager.addEventListener("gameOver", (event) => { showGameOverScreen(); }); ``` @@ -198,16 +272,18 @@ gameManager.addEventListener("gameOver", function(event) { Events propagate in three phases: -1. **Capture phase**: From root to target -2. **Target phase**: Processed at target -3. **Bubbling phase**: From target to root +1. **Capture phase**: From root to target (eventPhase = 1) +2. **Target phase**: Processed at target (eventPhase = 2) +3. **Bubbling phase**: From target to root (eventPhase = 3) + +```typescript +const { PointerEvent } = next2d.events; -```javascript // Process in capture phase -parent.addEventListener("click", handler, true); +parent.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // Process in bubbling phase (default) -child.addEventListener("click", handler, false); +child.addEventListener(PointerEvent.POINTER_DOWN, handler, false); ``` ## Related diff --git a/specs/en/movie-clip.md b/specs/en/movie-clip.md index a02e0c6f..f937c881 100644 --- a/specs/en/movie-clip.md +++ b/specs/en/movie-clip.md @@ -114,8 +114,9 @@ movieClip.addEventListener("exitFrame", function(event) { ### Basic Animation Control -```javascript +```typescript const { Loader } = next2d.display; +const { PointerEvent } = next2d.events; const { URLRequest } = next2d.net; // Load MovieClip from JSON @@ -129,7 +130,7 @@ stage.addChild(mc); mc.stop(); // Play/pause on button click -button.addEventListener("click", function() { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { if (mc.isPlaying) { mc.stop(); } else { diff --git a/specs/en/shape.md b/specs/en/shape.md index e644205f..0e25d3fd 100644 --- a/specs/en/shape.md +++ b/specs/en/shape.md @@ -358,19 +358,18 @@ stage.addChild(shape); #### Bitmap Fill -```javascript -const { Shape, Loader } = next2d.display; - -const loader = new Loader(); -await loader.load("texture.png"); +```typescript +const { Shape } = next2d.display; -const bitmapData = loader.contentLoaderInfo - .content.bitmapData; +// Load image using Shape's load() method +const textureShape = new Shape(); +await textureShape.load("texture.png"); +// Use the loaded bitmapData for bitmap fill const shape = new Shape(); const g = shape.graphics; -g.beginBitmapFill(bitmapData, null, true, true); +g.beginBitmapFill(textureShape.bitmapData, null, true, true); g.drawRect(0, 0, 400, 300); g.endFill(); diff --git a/specs/en/sprite.md b/specs/en/sprite.md index a9484175..8d8c890f 100644 --- a/specs/en/sprite.md +++ b/specs/en/sprite.md @@ -124,8 +124,9 @@ classDiagram ### Use as Button -```javascript +```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const button = new Sprite(); @@ -141,7 +142,7 @@ bg.graphics.endFill(); button.addChild(bg); // Click event -button.addEventListener("click", function() { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { console.log("Button clicked"); }); @@ -177,8 +178,9 @@ stage.addChild(maskShape); ### Drag and Drop -```javascript +```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const { Rectangle } = next2d.geom; const draggable = new Sprite(); @@ -191,13 +193,13 @@ bg.graphics.endFill(); draggable.addChild(bg); // Start drag -draggable.addEventListener("mouseDown", function() { +draggable.addEventListener(PointerEvent.POINTER_DOWN, () => { // Start dragging (lock center, specify bounds) draggable.startDrag(true, new Rectangle(0, 0, 400, 300)); }); // Stop drag -draggable.addEventListener("mouseUp", function() { +draggable.addEventListener(PointerEvent.POINTER_UP, () => { draggable.stopDrag(); }); diff --git a/specs/en/tween.md b/specs/en/tween.md index 864c2bb7..f2dc6211 100644 --- a/specs/en/tween.md +++ b/specs/en/tween.md @@ -1,6 +1,6 @@ # Tween Animation -Next2D Player allows you to implement programmatic animations (Tweens). You can smoothly animate properties like position, size, and transparency. +Next2D Player allows you to implement programmatic animations using the Tween system from the `@next2d/ui` package. You can smoothly animate properties like position, size, and transparency. ## Basic Tween Concepts @@ -10,364 +10,365 @@ flowchart LR Progress --> End["End Value"] subgraph Easing["Easing"] - Linear["Linear"] - EaseIn["EaseIn"] - EaseOut["EaseOut"] - EaseInOut["EaseInOut"] + Linear["linear"] + InQuad["inQuad"] + OutQuad["outQuad"] + InOutQuad["inOutQuad"] end ``` -## Basic Tween Class - -```javascript -class Tween { - constructor(target, options) { - this._target = target; - this._properties = {}; - this._duration = options.duration; - this._easing = options.easing || Easing.linear; - this._startTime = 0; - this._isPlaying = false; - this._onUpdate = options.onUpdate; - this._onComplete = options.onComplete; - } - - to(properties) { - for (const key in properties) { - this._properties[key] = { - start: this._target[key], - end: properties[key] - }; - } - return this; - } - - play() { - this._startTime = Date.now(); - this._isPlaying = true; - this._update(); - return this; - } - - _update() { - const self = this; - if (!this._isPlaying) return; - - const elapsed = Date.now() - this._startTime; - let progress = Math.min(1, elapsed / this._duration); - progress = this._easing(progress); - - // Update properties - for (const key in this._properties) { - const prop = this._properties[key]; - this._target[key] = prop.start + (prop.end - prop.start) * progress; - } - - if (this._onUpdate) { - this._onUpdate(); - } - - if (elapsed < this._duration) { - requestAnimationFrame(function() { self._update(); }); - } else { - this._isPlaying = false; - if (this._onComplete) { - this._onComplete(); - } - } - } - - stop() { - this._isPlaying = false; - } -} +## Tween.add() + +Use the `Tween.add()` method to create a `Job` instance for animation. + +```typescript +const { Tween, Easing } = next2d.ui; + +const job = Tween.add( + target, // Target object to animate + from, // Starting property values + to, // Ending property values + delay, // Delay in seconds (default: 0) + duration, // Animation duration in seconds (default: 1) + ease // Easing function (default: linear) +); + +// Start the animation +job.start(); ``` +### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `target` | any | - | Target object to animate | +| `from` | object | - | Starting property values | +| `to` | object | - | Ending property values | +| `delay` | number | 0 | Delay before animation starts (seconds) | +| `duration` | number | 1 | Animation duration (seconds) | +| `ease` | Function \| null | null | Easing function (defaults to linear) | + +### Return Value + +`Job` - Animation job instance + +## Job Class + +The Job class manages individual animation jobs. It extends EventDispatcher. + +### Methods + +| Method | Return | Description | +|--------|--------|-------------| +| `start()` | void | Starts the animation | +| `stop()` | void | Stops the animation | +| `chain(nextJob: Job \| null)` | Job \| null | Chains another job to start after this one completes | + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `target` | any | Target object | +| `from` | object | Start values | +| `to` | object | End values | +| `delay` | number | Delay time | +| `duration` | number | Duration time | +| `ease` | Function | Easing function | +| `currentTime` | number | Current animation time | +| `nextJob` | Job \| null | Next chained job | + +### Events + +| Event | Description | +|-------|-------------| +| `enterFrame` | Dispatched on each animation frame | +| `complete` | Dispatched when animation completes | + ## Easing Functions -```javascript -const Easing = { - // Linear - linear: function(t) { return t; }, - - // Acceleration - easeInQuad: function(t) { return t * t; }, - easeInCubic: function(t) { return t * t * t; }, - easeInQuart: function(t) { return t * t * t * t; }, - - // Deceleration - easeOutQuad: function(t) { return t * (2 - t); }, - easeOutCubic: function(t) { return (--t) * t * t + 1; }, - easeOutQuart: function(t) { return 1 - (--t) * t * t * t; }, - - // Acceleration → Deceleration - easeInOutQuad: function(t) { - return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; - }, - easeInOutCubic: function(t) { - return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; - }, - - // Bounce - easeOutBounce: function(t) { - if (t < 1 / 2.75) { - return 7.5625 * t * t; - } else if (t < 2 / 2.75) { - return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75; - } else if (t < 2.5 / 2.75) { - return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375; - } else { - return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375; - } - }, - - // Back (overshoots then returns) - easeOutBack: function(t) { - const c1 = 1.70158; - const c3 = c1 + 1; - return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); - }, - - // Elastic (rubber-like motion) - easeOutElastic: function(t) { - if (t === 0 || t === 1) return t; - return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI) / 3) + 1; - } -}; +The `Easing` class provides 32 easing functions across 11 easing types with In, Out, and InOut variants. + +### Linear +- `Easing.linear` - Constant speed + +### Quadratic (Quad) +- `Easing.inQuad` - Accelerating from zero velocity +- `Easing.outQuad` - Decelerating to zero velocity +- `Easing.inOutQuad` - Acceleration until halfway, then deceleration + +### Cubic +- `Easing.inCubic` / `Easing.outCubic` / `Easing.inOutCubic` + +### Quartic (Quart) +- `Easing.inQuart` / `Easing.outQuart` / `Easing.inOutQuart` + +### Quintic (Quint) +- `Easing.inQuint` / `Easing.outQuint` / `Easing.inOutQuint` + +### Sinusoidal (Sine) +- `Easing.inSine` / `Easing.outSine` / `Easing.inOutSine` + +### Exponential (Expo) +- `Easing.inExpo` / `Easing.outExpo` / `Easing.inOutExpo` + +### Circular (Circ) +- `Easing.inCirc` / `Easing.outCirc` / `Easing.inOutCirc` + +### Elastic +- `Easing.inElastic` / `Easing.outElastic` / `Easing.inOutElastic` + +### Back +- `Easing.inBack` / `Easing.outBack` / `Easing.inOutBack` + +### Bounce +- `Easing.inBounce` / `Easing.outBounce` / `Easing.inOutBounce` + +### Easing Function Parameters + +All easing functions accept four parameters: + +```typescript +ease(t: number, b: number, c: number, d: number): number ``` +- `t` - Current time (0 to d) +- `b` - Beginning value +- `c` - Change in value (end value - beginning value) +- `d` - Duration + ## Usage Examples ### Basic Movement Animation -```javascript -const { Sprite } = next2d.display; +```typescript +const { Tween, Easing } = next2d.ui; const sprite = new Sprite(); -sprite.x = 0; -sprite.y = 100; stage.addChild(sprite); -// Move right -new Tween(sprite, { duration: 1000, easing: Easing.easeOutQuad }) - .to({ x: 400 }) - .play(); +// Move x from 0 to 400 over 1 second +const job = Tween.add( + sprite, + { x: 0, y: 100 }, + { x: 400, y: 100 }, + 0, + 1, + Easing.outQuad +); + +job.start(); ``` ### Simultaneous Multi-Property Animation -```javascript +```typescript +const { Tween, Easing } = next2d.ui; + // Move + Scale + Fade in -new Tween(sprite, { - duration: 500, - easing: Easing.easeOutCubic -}) - .to({ - x: 200, - y: 150, - scaleX: 2, - scaleY: 2, - alpha: 1 - }) - .play(); +const job = Tween.add( + sprite, + { x: 0, y: 0, scaleX: 1, scaleY: 1, alpha: 0 }, + { x: 200, y: 150, scaleX: 2, scaleY: 2, alpha: 1 }, + 0, + 0.5, + Easing.outCubic +); + +job.start(); ``` -### Sequential Animation - -```javascript -// Consecutive animations -function sequentialAnimation(sprite) { - new Tween(sprite, { - duration: 500, - onComplete: function() { - new Tween(sprite, { - duration: 300, - onComplete: function() { - new Tween(sprite, { duration: 500 }) - .to({ alpha: 0 }) - .play(); - } - }) - .to({ scaleX: 1.5, scaleY: 1.5 }) - .play(); - } - }) - .to({ y: 100 }) - .play(); -} +### Chaining Animations + +```typescript +const { Tween, Easing } = next2d.ui; + +// First animation +const job1 = Tween.add( + sprite, + { x: 0 }, + { x: 100 }, + 0, 1, + Easing.outQuad +); + +// Second animation +const job2 = Tween.add( + sprite, + { x: 100 }, + { x: 200 }, + 0, 1, + Easing.inQuad +); + +// Chain and start +job1.chain(job2); +job1.start(); ``` -### Game Examples +### Delayed Animation -#### Character Jump +```typescript +const { Tween, Easing } = next2d.ui; -```javascript -function jump(character) { - const startY = character.y; - const jumpHeight = 100; +// Fade out over 1 second after 0.5 second delay +const job = Tween.add( + sprite, + { alpha: 1 }, + { alpha: 0 }, + 0.5, + 1, + Easing.inQuad +); - // Ascend - new Tween(character, { - duration: 300, - easing: Easing.easeOutQuad, - onComplete: function() { - // Descend - new Tween(character, { - duration: 300, - easing: Easing.easeInQuad - }) - .to({ y: startY }) - .play(); - } - }) - .to({ y: startY - jumpHeight }) - .play(); -} +job.start(); ``` -#### Damage Effect +### Using Events -```javascript -function damageEffect(target) { - const originalX = target.x; - let shakeCount = 0; +```typescript +const { Tween, Easing } = next2d.ui; - // Flash + Shake - function shake() { - if (shakeCount >= 6) { - target.x = originalX; - target.alpha = 1; - return; - } +const job = Tween.add( + sprite, + { x: 0 }, + { x: 300 }, + 0, 2, + Easing.inOutQuad +); - const offset = shakeCount % 2 === 0 ? 5 : -5; - target.x = originalX + offset; - target.alpha = shakeCount % 2 === 0 ? 0.5 : 1; - shakeCount++; +// Per-frame processing +job.addEventListener("enterFrame", (event) => { + console.log("Progress:", job.currentTime); +}); - setTimeout(shake, 50); - } +// On completion +job.addEventListener("complete", (event) => { + console.log("Animation complete!"); +}); - shake(); -} +job.start(); ``` -#### Coin Collect Effect +### Game Examples -```javascript -function coinCollectEffect(coin, targetY) { - // Float up and fade out - new Tween(coin, { - duration: 500, - easing: Easing.easeOutQuad, - onUpdate: function() { - // Rotate - coin.rotation += 15; - }, - onComplete: function() { - if (coin.parent) { - coin.parent.removeChild(coin); - } - } - }) - .to({ - y: targetY, - alpha: 0, - scaleX: 0.5, - scaleY: 0.5 - }) - .play(); +#### Character Jump + +```typescript +const { Tween, Easing } = next2d.ui; + +function jump(character) { + const startY = character.y; + const jumpHeight = 100; + + // Ascend + const upJob = Tween.add( + character, + { y: startY }, + { y: startY - jumpHeight }, + 0, 0.3, + Easing.outQuad + ); + + // Descend + const downJob = Tween.add( + character, + { y: startY - jumpHeight }, + { y: startY }, + 0, 0.3, + Easing.inQuad + ); + + // Chain ascend -> descend + upJob.chain(downJob); + upJob.start(); } ``` #### UI Animation -```javascript +```typescript +const { Tween, Easing } = next2d.ui; + function showPopup(popup) { popup.scaleX = 0; popup.scaleY = 0; popup.alpha = 0; - new Tween(popup, { - duration: 400, - easing: Easing.easeOutBack - }) - .to({ scaleX: 1, scaleY: 1, alpha: 1 }) - .play(); + const job = Tween.add( + popup, + { scaleX: 0, scaleY: 0, alpha: 0 }, + { scaleX: 1, scaleY: 1, alpha: 1 }, + 0, 0.4, + Easing.outBack + ); + + job.start(); } -function hidePopup(popup, onComplete) { - new Tween(popup, { - duration: 200, - easing: Easing.easeInQuad, - onComplete: onComplete - }) - .to({ scaleX: 0, scaleY: 0, alpha: 0 }) - .play(); +function hidePopup(popup) { + const job = Tween.add( + popup, + { scaleX: 1, scaleY: 1, alpha: 1 }, + { scaleX: 0, scaleY: 0, alpha: 0 }, + 0, 0.2, + Easing.inQuad + ); + + job.addEventListener("complete", () => { + popup.visible = false; + }); + + job.start(); } ``` -## Lightweight enterFrame-based Tween +#### Coin Collect Effect -```javascript -// Simple enterFrame-based tween -function tweenTo(target, property, endValue, speed) { - speed = speed || 0.1; +```typescript +const { Tween, Easing } = next2d.ui; - function handler(event) { - const current = target[property]; - const diff = endValue - current; +function coinCollectEffect(coin) { + const job = Tween.add( + coin, + { y: coin.y, alpha: 1, scaleX: 1, scaleY: 1 }, + { y: coin.y - 50, alpha: 0, scaleX: 0.5, scaleY: 0.5 }, + 0, 0.5, + Easing.outQuad + ); - if (Math.abs(diff) < 0.1) { - target[property] = endValue; - stage.removeEventListener("enterFrame", handler); - } else { - target[property] = current + diff * speed; - } - } + job.addEventListener("enterFrame", () => { + coin.rotation += 15; + }); - stage.addEventListener("enterFrame", handler); -} + job.addEventListener("complete", () => { + coin.parent?.removeChild(coin); + }); -// Usage -tweenTo(sprite, "x", 300, 0.15); // Move x toward 300 -tweenTo(sprite, "alpha", 0, 0.05); // Fade out + job.start(); +} ``` -## Custom Easing +### Stopping and Control -```javascript -// Bezier curve based easing -function bezierEasing(x1, y1, x2, y2) { - return function(t) { - // Simple cubic bezier interpolation - const cx = 3 * x1; - const bx = 3 * (x2 - x1) - cx; - const ax = 1 - cx - bx; +```typescript +const { Tween, Easing } = next2d.ui; - const cy = 3 * y1; - const by = 3 * (y2 - y1) - cy; - const ay = 1 - cy - by; +const job = Tween.add( + sprite, + { x: 0 }, + { x: 400 }, + 0, 2, + Easing.linear +); - function sampleCurveY(t) { - return ((ay * t + by) * t + cy) * t; - } +job.start(); - return sampleCurveY(t); - }; -} - -// CSS cubic-bezier equivalent -const customEase = bezierEasing(0.25, 0.1, 0.25, 1.0); +// Stop midway +stopButton.addEventListener(PointerEvent.POINTER_DOWN, () => { + job.stop(); +}); ``` -## Performance Tips - -1. **Use requestAnimationFrame**: Smoother than setTimeout -2. **Minimize Property Changes**: Only update necessary properties -3. **Object Pooling**: Pool and reuse tweens for many animations -4. **Cleanup After Completion**: Remove unnecessary listeners - ## Related - [DisplayObject](/en/reference/player/display-object) diff --git a/specs/en/video.md b/specs/en/video.md index 52304a25..1644f494 100644 --- a/specs/en/video.md +++ b/specs/en/video.md @@ -39,7 +39,7 @@ classDiagram | `duration` | number | 0 | Total number of keyframes (video duration) | | `currentTime` | number | 0 | Current keyframe (playback position) | | `volume` | number | 1 | The volume, ranging from 0 (silent) to 1 (full volume) | -| `loop` | boolean | false | Specifies whether to generate a video loop | +| `loop` | boolean | false | Specifies whether to loop the video | | `autoPlay` | boolean | true | Setting up automatic video playback | | `smoothing` | boolean | true | Specifies whether the video should be smoothed (interpolated) when it is scaled | | `paused` | boolean | true | Returns whether the video is paused | @@ -60,17 +60,19 @@ classDiagram ### Basic Video Playback -```javascript +```typescript const { Video } = next2d.media; -// Create Video object +// Create Video object (specify width, height) const video = new Video(640, 360); -// Set video source +// Set video source (loading starts automatically) video.src = "video.mp4"; -video.autoPlay = true; -video.loop = false; -video.volume = 0.8; + +// Property settings +video.autoPlay = true; // Auto play +video.loop = false; // No loop +video.smoothing = true; // Enable smoothing // Add to stage stage.addChild(video); @@ -78,43 +80,78 @@ stage.addChild(video); ### Playback Control -```javascript +```typescript const { Video } = next2d.media; +const { PointerEvent } = next2d.events; const video = new Video(640, 360); +video.autoPlay = false; // Disable auto play video.src = "video.mp4"; + stage.addChild(video); // Play button -playButton.addEventListener("click", async function() { +playButton.addEventListener(PointerEvent.POINTER_DOWN, async () => { await video.play(); }); // Pause button -pauseButton.addEventListener("click", function() { +pauseButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); }); // Stop button (pause and return to start) -stopButton.addEventListener("click", function() { +stopButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); video.seek(0); }); // Forward 10 seconds -forwardButton.addEventListener("click", function() { +forwardButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(video.currentTime + 10); }); // Back 10 seconds -backButton.addEventListener("click", function() { +backButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(Math.max(0, video.currentTime - 10)); }); ``` +### Event Listening + +```typescript +const { Video } = next2d.media; +const { VideoEvent } = next2d.events; + +const video = new Video(640, 360); + +// Play event +video.addEventListener(VideoEvent.PLAY, () => { + console.log("Play requested"); +}); + +// Playing event +video.addEventListener(VideoEvent.PLAYING, () => { + console.log("Playback started"); +}); + +// Pause event +video.addEventListener(VideoEvent.PAUSE, () => { + console.log("Paused"); +}); + +// Seek event +video.addEventListener(VideoEvent.SEEK, () => { + console.log("Seek:", video.currentTime); +}); + +video.src = "video.mp4"; +stage.addChild(video); +``` + ### Displaying Playback Progress -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); @@ -122,7 +159,7 @@ video.src = "video.mp4"; stage.addChild(video); // Update progress each frame -stage.addEventListener("enterFrame", function() { +stage.addEventListener("enterFrame", () => { if (video.duration > 0) { const progress = video.currentTime / video.duration; progressBar.scaleX = progress; @@ -139,129 +176,41 @@ function formatTime(seconds) { ### Volume Control -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); video.src = "video.mp4"; video.volume = 0.5; // 50% -stage.addChild(video); -// Volume slider -volumeSlider.addEventListener("change", function(event) { - video.volume = event.target.value; // 0.0 ~ 1.0 -}); +stage.addChild(video); // Mute toggle -let isMuted = false; -let previousVolume = 0.5; - -muteButton.addEventListener("click", function() { - isMuted = !isMuted; - if (isMuted) { - previousVolume = video.volume; - video.volume = 0; - } else { - video.volume = previousVolume; - } +muteButton.addEventListener(PointerEvent.POINTER_DOWN, () => { + video.muted = !video.muted; }); ``` -### Fullscreen Support +### Loop Playback -```javascript +```typescript const { Video } = next2d.media; const video = new Video(640, 360); +video.loop = true; // Enable loop video.src = "video.mp4"; -stage.addChild(video); -// Fullscreen toggle -fullscreenButton.addEventListener("click", function() { - if (stage.displayState === "normal") { - // Switch to fullscreen - stage.displayState = "fullScreen"; - video.width = stage.stageWidth; - video.height = stage.stageHeight; - } else { - // Return to normal display - stage.displayState = "normal"; - video.width = 640; - video.height = 360; - } -}); -``` - -### Video Player Component - -```javascript -const { Sprite } = next2d.display; -const { Video } = next2d.media; - -class VideoPlayer extends Sprite { - constructor(width, height) { - super(); - - this._width = width; - this._height = height; - - this._video = new Video(width, height); - this.addChild(this._video); - } - - load(url) { - this._video.src = url; - } - - async play() { - await this._video.play(); - } - - pause() { - this._video.pause(); - } - - seek(time) { - this._video.seek(time); - } - - get currentTime() { - return this._video.currentTime; - } - - get duration() { - return this._video.duration || 0; - } - - set volume(value) { - this._video.volume = value; - } - - get volume() { - return this._video.volume; - } -} - -// Usage -const player = new VideoPlayer(640, 360); -stage.addChild(player); -player.load("video.mp4"); -player.play(); +stage.addChild(video); ``` -### Loop Playback and Auto Play - -```javascript -const { Video } = next2d.media; +## VideoEvent -const video = new Video(640, 360); -video.src = "background-video.mp4"; -video.autoPlay = true; -video.loop = true; -video.volume = 0; // Muted background video - -stage.addChild(video); -``` +| Event | Description | +|-------|-------------| +| `VideoEvent.PLAY` | When play is requested | +| `VideoEvent.PLAYING` | When playback starts | +| `VideoEvent.PAUSE` | When paused | +| `VideoEvent.SEEK` | When seeking | ## Supported Formats diff --git a/specs/ja/events.md b/specs/ja/events.md index 4149a74a..31c2acf7 100644 --- a/specs/ja/events.md +++ b/specs/ja/events.md @@ -1,6 +1,6 @@ # イベントシステム -Next2D Playerは、Flash Playerと同様のイベントモデルを採用しています。 +Next2D Playerは、W3C DOMイベントモデルと同様の3フェーズイベントフロー機構を採用しています。 ## EventDispatcher @@ -11,15 +11,17 @@ Next2D Playerは、Flash Playerと同様のイベントモデルを採用して イベントリスナーを登録します。 ```typescript -displayObject.addEventListener("click", (event) => { - console.log("クリックされました"); +const { PointerEvent } = next2d.events; + +displayObject.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("ポインターが押されました"); }); // キャプチャフェーズで受け取る -displayObject.addEventListener("click", handler, true); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // 優先度を指定 -displayObject.addEventListener("click", handler, false, 10); +displayObject.addEventListener(PointerEvent.POINTER_DOWN, handler, false, 10); ``` ### removeEventListener(type, listener, useCapture) @@ -27,7 +29,15 @@ displayObject.addEventListener("click", handler, false, 10); イベントリスナーを削除します。 ```typescript -displayObject.removeEventListener("click", handler); +displayObject.removeEventListener(PointerEvent.POINTER_DOWN, handler); +``` + +### removeAllEventListener(type, useCapture) + +特定タイプのすべてのイベントリスナーを削除します。 + +```typescript +displayObject.removeAllEventListener(PointerEvent.POINTER_DOWN); ``` ### hasEventListener(type) @@ -35,8 +45,18 @@ displayObject.removeEventListener("click", handler); 指定タイプのリスナーが登録されているか確認します。 ```typescript -if (displayObject.hasEventListener("click")) { - console.log("クリックリスナーが登録されています"); +if (displayObject.hasEventListener(PointerEvent.POINTER_DOWN)) { + console.log("ポインターダウンリスナーが登録されています"); +} +``` + +### willTrigger(type) + +このオブジェクトまたは祖先がイベントタイプのリスナーを持つか確認します。 + +```typescript +if (displayObject.willTrigger(PointerEvent.POINTER_DOWN)) { + console.log("このオブジェクトまたは祖先にリスナーがあります"); } ``` @@ -62,7 +82,6 @@ displayObject.dispatchEvent(event); | `currentTarget` | Object | 現在のリスナー登録先 | | `eventPhase` | Number | イベントフェーズ | | `bubbles` | Boolean | バブリングするか | -| `cancelable` | Boolean | キャンセル可能か | ### メソッド @@ -70,7 +89,6 @@ displayObject.dispatchEvent(event); |----------|------| | `stopPropagation()` | 伝播を停止 | | `stopImmediatePropagation()` | 伝播を即座に停止 | -| `preventDefault()` | デフォルト動作をキャンセル | ## 標準イベントタイプ @@ -111,6 +129,7 @@ movieClip.addEventListener("enterFrame", (event) => { | `complete` | ロード完了 | | `progress` | ロード進捗 | | `ioError` | IOエラー | +| `httpStatus` | HTTPステータス受信 | ```typescript const { Loader } = next2d.display; @@ -130,39 +149,48 @@ loader.contentLoaderInfo.addEventListener("progress", (event) => { }); ``` -## マウスイベント +## ポインターイベント -| イベント | 説明 | -|----------|------| -| `click` | クリック | -| `doubleClick` | ダブルクリック | -| `mouseDown` | マウスボタン押下 | -| `mouseUp` | マウスボタン解放 | -| `mouseMove` | マウス移動 | -| `mouseOver` | マウスオーバー | -| `mouseOut` | マウスアウト | -| `rollOver` | ロールオーバー | -| `rollOut` | ロールアウト | +PointerEventはマウス、ペン、タッチなどのポインターデバイスの操作を統一的に処理します。 + +| イベント | 定数 | 説明 | +|----------|------|------| +| `pointerDown` | `PointerEvent.POINTER_DOWN` | ボタンの押下開始 | +| `pointerUp` | `PointerEvent.POINTER_UP` | ボタンの解放 | +| `pointerMove` | `PointerEvent.POINTER_MOVE` | ポインター座標の変化 | +| `pointerOver` | `PointerEvent.POINTER_OVER` | ポインターがヒットテスト境界に入った | +| `pointerOut` | `PointerEvent.POINTER_OUT` | ポインターがヒットテスト境界を出た | +| `pointerLeave` | `PointerEvent.POINTER_LEAVE` | ポインターが要素領域を離れた | +| `pointerCancel` | `PointerEvent.POINTER_CANCEL` | ポインター操作がキャンセルされた | +| `doubleClick` | `PointerEvent.DOUBLE_CLICK` | ダブルクリック/タップが発生 | ```typescript -sprite.addEventListener("click", (event) => { - console.log("クリック位置:", event.localX, event.localY); +const { PointerEvent } = next2d.events; + +sprite.addEventListener(PointerEvent.POINTER_DOWN, (event) => { + console.log("ポインターダウン:", event.localX, event.localY); }); -sprite.addEventListener("mouseMove", (event) => { - console.log("マウス位置:", event.stageX, event.stageY); +sprite.addEventListener(PointerEvent.POINTER_MOVE, (event) => { + console.log("ポインター移動:", event.stageX, event.stageY); +}); + +sprite.addEventListener(PointerEvent.DOUBLE_CLICK, (event) => { + console.log("ダブルクリック"); }); ``` ## キーボードイベント -| イベント | 説明 | -|----------|------| -| `keyDown` | キー押下 | -| `keyUp` | キー解放 | +| イベント | 定数 | 説明 | +|----------|------|------| +| `keyDown` | `KeyboardEvent.KEY_DOWN` | キー押下 | +| `keyUp` | `KeyboardEvent.KEY_UP` | キー解放 | ```typescript -stage.addEventListener("keyDown", (event) => { +const { KeyboardEvent } = next2d.events; + +stage.addEventListener(KeyboardEvent.KEY_DOWN, (event) => { console.log("キーコード:", event.keyCode); switch (event.keyCode) { @@ -176,6 +204,53 @@ stage.addEventListener("keyDown", (event) => { }); ``` +## フォーカスイベント + +| イベント | 定数 | 説明 | +|----------|------|------| +| `focusIn` | `FocusEvent.FOCUS_IN` | フォーカスを受け取った | +| `focusOut` | `FocusEvent.FOCUS_OUT` | フォーカスを失った | + +```typescript +const { FocusEvent } = next2d.events; + +textField.addEventListener(FocusEvent.FOCUS_IN, (event) => { + console.log("フォーカスを受け取りました"); +}); +``` + +## ホイールイベント + +| イベント | 定数 | 説明 | +|----------|------|------| +| `wheel` | `WheelEvent.WHEEL` | マウスホイールが回転した | + +```typescript +const { WheelEvent } = next2d.events; + +stage.addEventListener(WheelEvent.WHEEL, (event) => { + console.log("ホイール回転"); +}); +``` + +## ビデオイベント + +| イベント | 定数 | 説明 | +|----------|------|------| +| `play` | `VideoEvent.PLAY` | 再生がリクエストされた | +| `playing` | `VideoEvent.PLAYING` | 再生が開始された | +| `pause` | `VideoEvent.PAUSE` | 一時停止された | +| `seek` | `VideoEvent.SEEK` | シーク操作 | + +## ジョブイベント + +Tweenアニメーション用のイベントです。 + +| イベント | 定数 | 説明 | +|----------|------|------| +| `update` | `JobEvent.UPDATE` | プロパティが更新された | +| `stop` | `JobEvent.STOP` | ジョブが停止した | + ## カスタムイベント ```typescript @@ -197,16 +272,18 @@ gameManager.addEventListener("gameOver", (event) => { イベントは3つのフェーズで伝播します: -1. **キャプチャフェーズ**: rootからtargetへ -2. **ターゲットフェーズ**: targetで処理 -3. **バブリングフェーズ**: targetからrootへ +1. **キャプチャフェーズ**: rootからtargetへ(eventPhase = 1) +2. **ターゲットフェーズ**: targetで処理(eventPhase = 2) +3. **バブリングフェーズ**: targetからrootへ(eventPhase = 3) ```typescript +const { PointerEvent } = next2d.events; + // キャプチャフェーズで処理 -parent.addEventListener("click", handler, true); +parent.addEventListener(PointerEvent.POINTER_DOWN, handler, true); // バブリングフェーズで処理(デフォルト) -child.addEventListener("click", handler, false); +child.addEventListener(PointerEvent.POINTER_DOWN, handler, false); ``` ## 関連項目 diff --git a/specs/ja/movie-clip.md b/specs/ja/movie-clip.md index 981b124d..14fa3fcf 100644 --- a/specs/ja/movie-clip.md +++ b/specs/ja/movie-clip.md @@ -116,6 +116,7 @@ movieClip.addEventListener("exitFrame", (event) => { ```typescript const { Loader, Sprite } = next2d.display; +const { PointerEvent } = next2d.events; const { URLRequest } = next2d.net; // JSONからMovieClipを読み込み @@ -129,7 +130,7 @@ stage.addChild(mc); mc.stop(); // ボタンクリックで再生 -button.addEventListener("click", () => { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { if (mc.isPlaying) { mc.stop(); } else { diff --git a/specs/ja/shape.md b/specs/ja/shape.md index a1c33589..d3d46a17 100644 --- a/specs/ja/shape.md +++ b/specs/ja/shape.md @@ -359,18 +359,17 @@ stage.addChild(shape); #### ビットマップ塗りつぶし ```typescript -const { Shape, Loader } = next2d.display; - -const loader = new Loader(); -await loader.load("texture.png"); +const { Shape } = next2d.display; -const bitmapData = loader.contentLoaderInfo - .content.bitmapData; +// Shapeのload()メソッドで画像を読み込み +const textureShape = new Shape(); +await textureShape.load("texture.png"); +// 読み込んだbitmapDataを使ってビットマップ塗りつぶし const shape = new Shape(); const g = shape.graphics; -g.beginBitmapFill(bitmapData, null, true, true); +g.beginBitmapFill(textureShape.bitmapData, null, true, true); g.drawRect(0, 0, 400, 300); g.endFill(); diff --git a/specs/ja/sprite.md b/specs/ja/sprite.md index 8ca7e5b3..90d377ec 100644 --- a/specs/ja/sprite.md +++ b/specs/ja/sprite.md @@ -126,6 +126,7 @@ classDiagram ```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const button = new Sprite(); @@ -141,7 +142,7 @@ bg.graphics.endFill(); button.addChild(bg); // クリックイベント -button.addEventListener("click", () => { +button.addEventListener(PointerEvent.POINTER_DOWN, () => { console.log("ボタンがクリックされました"); }); @@ -179,6 +180,7 @@ stage.addChild(maskShape); ```typescript const { Sprite, Shape } = next2d.display; +const { PointerEvent } = next2d.events; const { Rectangle } = next2d.geom; const draggable = new Sprite(); @@ -191,13 +193,13 @@ bg.graphics.endFill(); draggable.addChild(bg); // ドラッグ開始 -draggable.addEventListener("mouseDown", () => { +draggable.addEventListener(PointerEvent.POINTER_DOWN, () => { // ドラッグを開始(中心をロック、境界を指定) draggable.startDrag(true, new Rectangle(0, 0, 400, 300)); }); // ドラッグ終了 -draggable.addEventListener("mouseUp", () => { +draggable.addEventListener(PointerEvent.POINTER_UP, () => { draggable.stopDrag(); }); diff --git a/specs/ja/tween.md b/specs/ja/tween.md index feae465a..e622d485 100644 --- a/specs/ja/tween.md +++ b/specs/ja/tween.md @@ -1,6 +1,6 @@ # Tweenアニメーション -Next2D Playerでは、プログラムによるアニメーション(Tween)を実装できます。位置、サイズ、透明度などのプロパティを滑らかに変化させることができます。 +Next2D Playerでは、`@next2d/ui`パッケージのTweenシステムを使用して、プログラムによるアニメーションを実装できます。位置、サイズ、透明度などのプロパティを滑らかに変化させることができます。 ## Tweenの基本概念 @@ -10,361 +10,364 @@ flowchart LR Progress --> End["終了値"] subgraph Easing["イージング"] - Linear["Linear"] - EaseIn["EaseIn"] - EaseOut["EaseOut"] - EaseInOut["EaseInOut"] + Linear["linear"] + InQuad["inQuad"] + OutQuad["outQuad"] + InOutQuad["inOutQuad"] end ``` -## 基本的なTweenクラス +## Tween.add() + +`Tween.add()`メソッドでアニメーション用の`Job`インスタンスを作成します。 ```typescript -class Tween { - private _target; - private _properties = {}; - private _duration; - private _easing; - private _startTime = 0; - private _isPlaying = false; - private _onUpdate; - private _onComplete; - - constructor(target, options) { - this._target = target; - this._duration = options.duration; - this._easing = options.easing || Easing.linear; - this._onUpdate = options.onUpdate; - this._onComplete = options.onComplete; - } - - to(properties) { - for (const key in properties) { - this._properties[key] = { - start: this._target[key], - end: properties[key] - }; - } - return this; - } - - play() { - this._startTime = Date.now(); - this._isPlaying = true; - this._update(); - return this; - } - - private _update = () => { - if (!this._isPlaying) return; - - const elapsed = Date.now() - this._startTime; - let progress = Math.min(1, elapsed / this._duration); - progress = this._easing(progress); - - // プロパティを更新 - for (const key in this._properties) { - const prop = this._properties[key]; - this._target[key] = prop.start + (prop.end - prop.start) * progress; - } - - if (this._onUpdate) { - this._onUpdate(); - } - - if (elapsed < this._duration) { - requestAnimationFrame(this._update); - } else { - this._isPlaying = false; - if (this._onComplete) { - this._onComplete(); - } - } - }; - - stop() { - this._isPlaying = false; - } -} +const { Tween, Easing } = next2d.ui; + +const job = Tween.add( + target, // アニメーション対象オブジェクト + from, // 開始プロパティ値 + to, // 終了プロパティ値 + delay, // 遅延時間(秒、デフォルト: 0) + duration, // アニメーション時間(秒、デフォルト: 1) + ease // イージング関数(デフォルト: linear) +); + +// アニメーションを開始 +job.start(); ``` +### パラメータ + +| パラメータ | 型 | デフォルト | 説明 | +|-----------|------|----------|------| +| `target` | any | - | アニメーション対象オブジェクト | +| `from` | object | - | 開始プロパティ値 | +| `to` | object | - | 終了プロパティ値 | +| `delay` | number | 0 | アニメーション開始前の遅延(秒) | +| `duration` | number | 1 | アニメーション継続時間(秒) | +| `ease` | Function \| null | null | イージング関数(デフォルトはlinear) | + +### 戻り値 + +`Job` - アニメーションジョブインスタンス + +## Job クラス + +Jobクラスは個別のアニメーションジョブを管理します。EventDispatcherを継承しています。 + +### メソッド + +| メソッド | 戻り値 | 説明 | +|---------|--------|------| +| `start()` | void | アニメーションを開始します | +| `stop()` | void | アニメーションを停止します | +| `chain(nextJob: Job \| null)` | Job \| null | このジョブの完了後に別のジョブを連結します | + +### プロパティ + +| プロパティ | 型 | 説明 | +|-----------|------|------| +| `target` | any | 対象オブジェクト | +| `from` | object | 開始値 | +| `to` | object | 終了値 | +| `delay` | number | 遅延時間 | +| `duration` | number | 継続時間 | +| `ease` | Function | イージング関数 | +| `currentTime` | number | 現在のアニメーション時間 | +| `nextJob` | Job \| null | 次の連結ジョブ | + +### イベント + +| イベント | 説明 | +|----------|------| +| `enterFrame` | 各アニメーションフレームで発行 | +| `complete` | アニメーション完了時に発行 | + ## イージング関数 +`Easing`クラスは、11種類のイージングタイプでIn、Out、InOutのバリエーションを含む32種類のイージング関数を提供します。 + +### Linear / リニア +- `Easing.linear` - 一定速度 + +### Quadratic (Quad) / 二次関数 +- `Easing.inQuad` - ゼロ速度から加速 +- `Easing.outQuad` - ゼロ速度まで減速 +- `Easing.inOutQuad` - 中間まで加速、その後減速 + +### Cubic / 三次関数 +- `Easing.inCubic` / `Easing.outCubic` / `Easing.inOutCubic` + +### Quartic (Quart) / 四次関数 +- `Easing.inQuart` / `Easing.outQuart` / `Easing.inOutQuart` + +### Quintic (Quint) / 五次関数 +- `Easing.inQuint` / `Easing.outQuint` / `Easing.inOutQuint` + +### Sinusoidal (Sine) / 正弦波 +- `Easing.inSine` / `Easing.outSine` / `Easing.inOutSine` + +### Exponential (Expo) / 指数関数 +- `Easing.inExpo` / `Easing.outExpo` / `Easing.inOutExpo` + +### Circular (Circ) / 円形 +- `Easing.inCirc` / `Easing.outCirc` / `Easing.inOutCirc` + +### Elastic / 弾性 +- `Easing.inElastic` / `Easing.outElastic` / `Easing.inOutElastic` + +### Back / バック +- `Easing.inBack` / `Easing.outBack` / `Easing.inOutBack` + +### Bounce / バウンス +- `Easing.inBounce` / `Easing.outBounce` / `Easing.inOutBounce` + +### イージング関数のパラメータ + +すべてのイージング関数は4つのパラメータを受け取ります: + ```typescript -const Easing = { - // 線形 - linear: (t) => t, - - // 加速 - easeInQuad: (t) => t * t, - easeInCubic: (t) => t * t * t, - easeInQuart: (t) => t * t * t * t, - - // 減速 - easeOutQuad: (t) => t * (2 - t), - easeOutCubic: (t) => (--t) * t * t + 1, - easeOutQuart: (t) => 1 - (--t) * t * t * t, - - // 加速→減速 - easeInOutQuad: (t) => - t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t, - easeInOutCubic: (t) => - t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1, - - // バウンス - easeOutBounce: (t) => { - if (t < 1 / 2.75) { - return 7.5625 * t * t; - } else if (t < 2 / 2.75) { - return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75; - } else if (t < 2.5 / 2.75) { - return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375; - } else { - return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375; - } - }, - - // バック(行き過ぎて戻る) - easeOutBack: (t) => { - const c1 = 1.70158; - const c3 = c1 + 1; - return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); - }, - - // エラスティック(ゴムのような動き) - easeOutElastic: (t) => { - if (t === 0 || t === 1) return t; - return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * (2 * Math.PI) / 3) + 1; - } -}; +ease(t: number, b: number, c: number, d: number): number ``` +- `t` - 現在の時間 (0 to d) +- `b` - 開始値 +- `c` - 変化量 (終了値 - 開始値) +- `d` - 継続時間 + ## 使用例 ### 基本的な移動アニメーション ```typescript -const { Sprite } = next2d.display; +const { Tween, Easing } = next2d.ui; const sprite = new Sprite(); -sprite.x = 0; -sprite.y = 100; stage.addChild(sprite); -// 右に移動 -new Tween(sprite, { duration: 1000, easing: Easing.easeOutQuad }) - .to({ x: 400 }) - .play(); +// xを0から400に1秒かけて移動 +const job = Tween.add( + sprite, + { x: 0, y: 100 }, + { x: 400, y: 100 }, + 0, + 1, + Easing.outQuad +); + +job.start(); ``` ### 複数プロパティの同時アニメーション ```typescript +const { Tween, Easing } = next2d.ui; + // 移動 + 拡大 + フェードイン -new Tween(sprite, { - duration: 500, - easing: Easing.easeOutCubic -}) - .to({ - x: 200, - y: 150, - scaleX: 2, - scaleY: 2, - alpha: 1 - }) - .play(); +const job = Tween.add( + sprite, + { x: 0, y: 0, scaleX: 1, scaleY: 1, alpha: 0 }, + { x: 200, y: 150, scaleX: 2, scaleY: 2, alpha: 1 }, + 0, + 0.5, + Easing.outCubic +); + +job.start(); ``` -### シーケンシャルアニメーション +### アニメーションの連結 (chain) ```typescript -// 連続したアニメーション -function sequentialAnimation(sprite) { - new Tween(sprite, { - duration: 500, - onComplete: () => { - new Tween(sprite, { - duration: 300, - onComplete: () => { - new Tween(sprite, { duration: 500 }) - .to({ alpha: 0 }) - .play(); - } - }) - .to({ scaleX: 1.5, scaleY: 1.5 }) - .play(); - } - }) - .to({ y: 100 }) - .play(); -} +const { Tween, Easing } = next2d.ui; + +// 最初のアニメーション +const job1 = Tween.add( + sprite, + { x: 0 }, + { x: 100 }, + 0, 1, + Easing.outQuad +); + +// 2つ目のアニメーション +const job2 = Tween.add( + sprite, + { x: 100 }, + { x: 200 }, + 0, 1, + Easing.inQuad +); + +// 連結して実行 +job1.chain(job2); +job1.start(); ``` -### ゲームでの活用例 - -#### キャラクタージャンプ +### 遅延付きアニメーション ```typescript -function jump(character) { - const startY = character.y; - const jumpHeight = 100; - - // 上昇 - new Tween(character, { - duration: 300, - easing: Easing.easeOutQuad, - onComplete: () => { - // 下降 - new Tween(character, { - duration: 300, - easing: Easing.easeInQuad - }) - .to({ y: startY }) - .play(); - } - }) - .to({ y: startY - jumpHeight }) - .play(); -} +const { Tween, Easing } = next2d.ui; + +// 0.5秒遅延後に1秒かけてフェードアウト +const job = Tween.add( + sprite, + { alpha: 1 }, + { alpha: 0 }, + 0.5, + 1, + Easing.inQuad +); + +job.start(); ``` -#### ダメージエフェクト +### イベントの活用 ```typescript -function damageEffect(target) { - const originalX = target.x; - let shakeCount = 0; - - // 点滅 + 揺れ - const shake = () => { - if (shakeCount >= 6) { - target.x = originalX; - target.alpha = 1; - return; - } - - const offset = shakeCount % 2 === 0 ? 5 : -5; - target.x = originalX + offset; - target.alpha = shakeCount % 2 === 0 ? 0.5 : 1; - shakeCount++; - - setTimeout(shake, 50); - }; - - shake(); -} +const { Tween, Easing } = next2d.ui; + +const job = Tween.add( + sprite, + { x: 0 }, + { x: 300 }, + 0, 2, + Easing.inOutQuad +); + +// フレームごとの処理 +job.addEventListener("enterFrame", (event) => { + console.log("進行中:", job.currentTime); +}); + +// 完了時の処理 +job.addEventListener("complete", (event) => { + console.log("アニメーション完了!"); +}); + +job.start(); ``` -#### コイン取得エフェクト +### ゲームでの活用例 + +#### キャラクタージャンプ ```typescript -function coinCollectEffect(coin, targetY) { - // 上に飛んでフェードアウト - new Tween(coin, { - duration: 500, - easing: Easing.easeOutQuad, - onUpdate: () => { - // 回転 - coin.rotation += 15; - }, - onComplete: () => { - coin.parent?.removeChild(coin); - } - }) - .to({ - y: targetY, - alpha: 0, - scaleX: 0.5, - scaleY: 0.5 - }) - .play(); +const { Tween, Easing } = next2d.ui; + +function jump(character) { + const startY = character.y; + const jumpHeight = 100; + + // 上昇 + const upJob = Tween.add( + character, + { y: startY }, + { y: startY - jumpHeight }, + 0, 0.3, + Easing.outQuad + ); + + // 下降 + const downJob = Tween.add( + character, + { y: startY - jumpHeight }, + { y: startY }, + 0, 0.3, + Easing.inQuad + ); + + // 上昇 → 下降を連結 + upJob.chain(downJob); + upJob.start(); } ``` #### UI表示アニメーション ```typescript +const { Tween, Easing } = next2d.ui; + function showPopup(popup) { popup.scaleX = 0; popup.scaleY = 0; popup.alpha = 0; - new Tween(popup, { - duration: 400, - easing: Easing.easeOutBack - }) - .to({ scaleX: 1, scaleY: 1, alpha: 1 }) - .play(); -} + const job = Tween.add( + popup, + { scaleX: 0, scaleY: 0, alpha: 0 }, + { scaleX: 1, scaleY: 1, alpha: 1 }, + 0, 0.4, + Easing.outBack + ); -function hidePopup(popup, onComplete) { - new Tween(popup, { - duration: 200, - easing: Easing.easeInQuad, - onComplete - }) - .to({ scaleX: 0, scaleY: 0, alpha: 0 }) - .play(); + job.start(); } -``` -## enterFrameを使った軽量Tween +function hidePopup(popup) { + const job = Tween.add( + popup, + { scaleX: 1, scaleY: 1, alpha: 1 }, + { scaleX: 0, scaleY: 0, alpha: 0 }, + 0, 0.2, + Easing.inQuad + ); -```typescript -// シンプルなenterFrameベースのTween -function tweenTo(target, property, endValue, speed = 0.1) { - const handler = (event) => { - const current = target[property]; - const diff = endValue - current; - - if (Math.abs(diff) < 0.1) { - target[property] = endValue; - stage.removeEventListener("enterFrame", handler); - } else { - target[property] = current + diff * speed; - } - }; - - stage.addEventListener("enterFrame", handler); -} + job.addEventListener("complete", () => { + popup.visible = false; + }); -// 使用例 -tweenTo(sprite, "x", 300, 0.15); // xを300に向かって移動 -tweenTo(sprite, "alpha", 0, 0.05); // フェードアウト + job.start(); +} ``` -## カスタムイージング +#### コイン取得エフェクト ```typescript -// ベジェ曲線ベースのイージング -function bezierEasing(x1, y1, x2, y2) { - return (t) => { - // 簡易的な3次ベジェ補間 - const cx = 3 * x1; - const bx = 3 * (x2 - x1) - cx; - const ax = 1 - cx - bx; - - const cy = 3 * y1; - const by = 3 * (y2 - y1) - cy; - const ay = 1 - cy - by; - - const sampleCurveY = (t) => - ((ay * t + by) * t + cy) * t; - - return sampleCurveY(t); - }; +const { Tween, Easing } = next2d.ui; + +function coinCollectEffect(coin) { + const job = Tween.add( + coin, + { y: coin.y, alpha: 1, scaleX: 1, scaleY: 1 }, + { y: coin.y - 50, alpha: 0, scaleX: 0.5, scaleY: 0.5 }, + 0, 0.5, + Easing.outQuad + ); + + job.addEventListener("enterFrame", () => { + coin.rotation += 15; + }); + + job.addEventListener("complete", () => { + coin.parent?.removeChild(coin); + }); + + job.start(); } - -// CSS cubic-bezier相当 -const customEase = bezierEasing(0.25, 0.1, 0.25, 1.0); ``` -## パフォーマンスのヒント +### 停止と制御 -1. **requestAnimationFrame使用**: setTimeoutよりもスムーズ -2. **プロパティ変更の最小化**: 必要なプロパティのみ更新 -3. **オブジェクトプール**: 大量のTweenはプールして再利用 -4. **完了後のクリーンアップ**: 不要なリスナーは削除 +```typescript +const { Tween, Easing } = next2d.ui; + +const job = Tween.add( + sprite, + { x: 0 }, + { x: 400 }, + 0, 2, + Easing.linear +); + +job.start(); + +// 途中で停止 +stopButton.addEventListener("pointerDown", () => { + job.stop(); +}); +``` ## 関連項目 diff --git a/specs/ja/video.md b/specs/ja/video.md index 3110bc69..3608dddc 100644 --- a/specs/ja/video.md +++ b/specs/ja/video.md @@ -81,7 +81,8 @@ stage.addChild(video); ### 再生コントロール ```typescript -const { Video, VideoEvent } = next2d.media; +const { Video } = next2d.media; +const { PointerEvent } = next2d.events; const video = new Video(640, 360); video.autoPlay = false; // 自動再生を無効化 @@ -90,28 +91,28 @@ video.src = "video.mp4"; stage.addChild(video); // 再生ボタン -playButton.addEventListener("click", async () => { +playButton.addEventListener(PointerEvent.POINTER_DOWN, async () => { await video.play(); }); // 一時停止ボタン -pauseButton.addEventListener("click", () => { +pauseButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); }); // 停止ボタン(先頭に戻って停止) -stopButton.addEventListener("click", () => { +stopButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.pause(); video.seek(0); }); // 10秒進む -forwardButton.addEventListener("click", () => { +forwardButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(video.currentTime + 10); }); // 10秒戻る -backButton.addEventListener("click", () => { +backButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.seek(Math.max(0, video.currentTime - 10)); }); ``` @@ -119,18 +120,18 @@ backButton.addEventListener("click", () => { ### イベントリスニング ```typescript -const { Video, VideoEvent } = next2d.media; +const { Video } = next2d.media; +const { VideoEvent } = next2d.events; const video = new Video(640, 360); -// メタデータ受信イベント -video.addEventListener(VideoEvent.METADATA_RECEIVED, () => { - console.log("Duration:", video.duration); - console.log("Size:", video.videoWidth, "x", video.videoHeight); -}); - // 再生イベント video.addEventListener(VideoEvent.PLAY, () => { + console.log("再生リクエスト"); +}); + +// 再生開始イベント +video.addEventListener(VideoEvent.PLAYING, () => { console.log("再生開始"); }); @@ -144,11 +145,6 @@ video.addEventListener(VideoEvent.SEEK, () => { console.log("シーク:", video.currentTime); }); -// 終了イベント -video.addEventListener(VideoEvent.ENDED, () => { - console.log("再生終了"); -}); - video.src = "video.mp4"; stage.addChild(video); ``` @@ -156,7 +152,7 @@ stage.addChild(video); ### 再生進捗の表示 ```typescript -const { Video, VideoEvent } = next2d.media; +const { Video } = next2d.media; const video = new Video(640, 360); video.src = "video.mp4"; @@ -189,13 +185,8 @@ video.volume = 0.5; // 50% stage.addChild(video); -// 音量スライダー -volumeSlider.addEventListener("change", (event) => { - video.volume = event.target.value; // 0.0 ~ 1.0 -}); - // ミュートトグル -muteButton.addEventListener("click", () => { +muteButton.addEventListener(PointerEvent.POINTER_DOWN, () => { video.muted = !video.muted; }); ``` @@ -216,11 +207,10 @@ stage.addChild(video); | イベント | 説明 | |----------|------| -| `VideoEvent.METADATA_RECEIVED` | メタデータ受信時 | -| `VideoEvent.PLAY` | 再生開始時 | +| `VideoEvent.PLAY` | 再生がリクエストされた時 | +| `VideoEvent.PLAYING` | 再生が開始された時 | | `VideoEvent.PAUSE` | 一時停止時 | | `VideoEvent.SEEK` | シーク時 | -| `VideoEvent.ENDED` | 再生終了時 | ## サポートフォーマット