UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

232 lines (214 loc) • 9 kB
import { AnimationClip } from "three"; import { ScaleClipType } from "../../engine/engine_animation.js"; import { AnimationUtils } from "../../engine/engine_animation.js"; import { serializable } from "../../engine/engine_serialization_decorator.js"; import { registerType } from "../../engine/engine_typestore.js"; import { Animation } from "../Animation.js"; import { Behaviour } from "../Component.js"; /** * [HoverAnimation](https://engine.needle.tools/docs/api/HoverAnimation) plays animations in response to pointer hover events on the object this component is attached to. * The component automatically detects when the mouse pointer or touch enters/exits the object or any of its children, triggering the corresponding animations. * * **How It Works:** * The component listens to pointer enter and exit events and switches between two animation states: * - **Hover state**: Plays when the pointer enters the object (default: scale up to 110%) * - **Idle state**: Plays when the pointer exits the object (default: returns to original scale) * * **Default Behavior:** * If no custom animation clips are provided, the component automatically creates a smooth scale-up animation using the * {@link type}, {@link duration}, and {@link scaleFactor} properties. This provides instant hover feedback without * requiring any animation setup. * * **Custom Animations:** * You can provide your own animation clips for complete control over the hover effect. This allows you to create * complex animations involving position, rotation, color changes, or any other animated property. * * **Common Use Cases:** * - Interactive buttons with scale feedback * - Product showcases with highlight animations * - Menu items with hover effects * - Interactive 3D objects in AR/VR experiences * - Call-to-action elements with attention-grabbing animations * * @example Basic usage with default scale animation * ```ts * const button = new Object3D(); * button.addComponent(HoverAnimation, { * scaleFactor: 1.2, // Scale to 120% on hover * duration: 0.2, // 200ms animation * type: "ease-in-out" // Smooth easing * }); * scene.add(button); * ``` * * @example Custom hover animations * ```ts * const obj = new Object3D(); * const hoverAnim = loadAnimationClip("hover-glow.anim"); * const idleAnim = loadAnimationClip("idle-pulse.anim"); * * obj.addComponent(HoverAnimation, { * hovered: hoverAnim, // Custom hover animation * idle: idleAnim // Custom idle animation * }); * scene.add(obj); * ``` * * @example Quick scale animation with custom settings * ```ts * gameObject.addComponent(HoverAnimation, { * scaleFactor: 1.15, * duration: 0.15, * type: "ease-out" * }); * ``` * * @see {@link Animation} - The underlying animation component used to play clips * @see {@link AnimationClip} - For creating custom animation clips * @see {@link AnimationUtils} - Utility functions for creating animations programmatically * @see {@link ScaleClipType} - Available easing types for the default scale animation * @see {@link ObjectRaycaster} - Controls which objects receive pointer events * @see {@link PointerEvents} - For more complex pointer interaction handling * * @summary Plays animations on pointer hover enter/exit events * @category Interactivity * @group Components * @component */ @registerType export class HoverAnimation extends Behaviour { /** * The easing type for the default scale animation. * * This property controls how the scale animation interpolates from the start to end value. * Different easing types create different "feels" for the hover effect. * * **Available types:** * - `"linear"`: Constant speed throughout the animation * - `"ease-in"`: Starts slow, ends fast * - `"ease-out"`: Starts fast, ends slow (good for responsive feel) * - `"ease-in-out"`: Starts slow, fast in middle, ends slow (smooth and natural) * * **Note:** This is only used when no custom {@link hovered} animation clip is provided. * If you provide a custom animation clip, this property is ignored. * * @see {@link ScaleClipType} for all available easing types * @default "linear" */ @serializable() type: ScaleClipType = "linear"; /** * Duration of the default hover animation in seconds. * * This controls how long it takes for the object to scale up when hovered. * Shorter durations feel more responsive, while longer durations feel smoother. * * **Recommendations:** * - `0.1-0.15s`: Snappy, responsive feel (good for buttons) * - `0.2-0.3s`: Smooth, noticeable animation * - `0.4s+`: Slow, emphasized effect * * **Note:** This is only used when no custom {@link hovered} animation clip is provided. * If you provide a custom animation clip, this property is ignored. * * @default 0.1 */ @serializable() duration: number = 0.1; /** * The scale multiplier to apply when the object is hovered. * * This value is multiplied with the object's original scale to determine the hover size. * A value of `1.0` means no change, values greater than `1.0` scale up, and values less than `1.0` scale down. * * **Examples:** * - `1.0`: No scale change * - `1.1`: Scale to 110% (subtle effect, default) * - `1.2`: Scale to 120% (noticeable effect) * - `1.5`: Scale to 150% (dramatic effect) * - `0.9`: Scale to 90% (shrink on hover) * * **Note:** This is only used when no custom {@link hovered} animation clip is provided. * If you provide a custom animation clip, this property is ignored. * * @default 1.1 */ @serializable() scaleFactor: number = 1.1; /** * Custom animation clip to play when the pointer hovers over the object. * * If `null`, the component automatically generates a scale-up animation based on the * {@link type}, {@link duration}, and {@link scaleFactor} properties. * * Provide a custom animation clip if you want more complex hover effects such as: * - Color changes or material property animations * - Position or rotation changes * - Multi-property animations * - Animations affecting child objects * * **Tip:** The animation plays with a 0.1s fade duration for smooth transitions. * * @see {@link AnimationClip} for creating custom animation clips * @see {@link AnimationUtils.createScaleClip} for programmatically creating scale animations * @default null (generates default scale animation) */ @serializable(AnimationClip) hovered: AnimationClip | null = null; /** * Custom animation clip to play when the pointer is not hovering over the object (idle state). * * If `null`, an empty animation clip is used, which returns the object to its original state * when the hover animation ends. * * You can provide a custom idle animation for effects such as: * - Subtle breathing or floating motion when not hovered * - Pulsing or glowing effects in idle state * - Return-to-normal animations with custom easing * - Looping ambient animations * * **Tip:** The idle animation is played with `loop: true`, so it will repeat continuously * until the object is hovered again. * * @see {@link AnimationClip} for creating custom animation clips * @see {@link AnimationUtils.emptyClip} to see how the default empty clip is created * @default null (uses empty clip that returns to original state) */ @serializable(AnimationClip) idle: AnimationClip | null = null; private animation: Animation | null = null; start() { if (!this.idle) this.idle = AnimationUtils.emptyClip(); if (!this.hovered || !(this.hovered instanceof AnimationClip)) { this.hovered = AnimationUtils.createScaleClip({ type: "linear", duration: this.duration || 0.1, scale: this.gameObject.scale, scaleFactor: this.scaleFactor || 1.1, }); } this.animation ??= this.gameObject.addComponent(Animation); this.animation.playAutomatically = false; this.playIdle(); } onEnable() { if (this.animation) this.animation.enabled = true; this.playIdle(); } onDisable() { if (this.animation) this.animation.enabled = false; this.playIdle(); } onPointerEnter() { this.playHover(); } onPointerExit() { this.playIdle(); } private playIdle() { if (this.idle) this.animation?.play(this.idle, { exclusive: true, fadeDuration: .1, loop: true }); } private playHover() { if (this.hovered) this.animation?.play(this.hovered, { exclusive: true, fadeDuration: .1, loop: false, clampWhenFinished: true }); } }