[{"data":1,"prerenderedAt":525},["ShallowReactive",2],{"navigation_docs":3,"i-nanime:logo":163,"-examples-player":170,"-examples-player-surround":494,"preview-examples\u002Fplayer\u002FPlayerDemo.vue-false":499,"mdc-bf975v-key":499},[4,35,95,115,129,153],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fgetting-started","1.getting-started",[10,15,20,25,30],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-ph-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-ph-download-simple",{"title":21,"path":22,"stem":23,"icon":24},"Configuration","\u002Fgetting-started\u002Fconfiguration","1.getting-started\u002F3.configuration","i-ph-sliders-horizontal",{"title":26,"path":27,"stem":28,"icon":29},"Performance","\u002Fgetting-started\u002Fperformance","1.getting-started\u002F4.performance","i-ph-gauge",{"title":31,"path":32,"stem":33,"icon":34},"Accessibility","\u002Fgetting-started\u002Faccessibility","1.getting-started\u002F5.accessibility","i-ph-person-simple-circle",{"title":36,"icon":6,"path":37,"stem":38,"children":39,"page":6},"Composables","\u002Fcomposables","2.composables",[40,44,49,54,59,64,69,74,79,84,90],{"title":11,"path":41,"stem":42,"icon":43},"\u002Fcomposables\u002Fintroduction","2.composables\u002F0.introduction","i-ph-stack",{"title":45,"path":46,"stem":47,"icon":48},"useAnimate","\u002Fcomposables\u002Fuse-animate","2.composables\u002F1.use-animate","i-ph-play-circle",{"title":50,"path":51,"stem":52,"icon":53},"useWaapiAnimate","\u002Fcomposables\u002Fuse-waapi-animate","2.composables\u002F2.use-waapi-animate","i-ph-lightning",{"title":55,"path":56,"stem":57,"icon":58},"useAnimatable","\u002Fcomposables\u002Fuse-animatable","2.composables\u002F3.use-animatable","i-ph-cube",{"title":60,"path":61,"stem":62,"icon":63},"useSplitText","\u002Fcomposables\u002Fuse-split-text","2.composables\u002F4.use-split-text","i-ph-text-t",{"title":65,"path":66,"stem":67,"icon":68},"useScrambleText","\u002Fcomposables\u002Fuse-scramble-text","2.composables\u002F5.use-scramble-text","i-ph-shuffle",{"title":70,"path":71,"stem":72,"icon":73},"useDraggable","\u002Fcomposables\u002Fuse-draggable","2.composables\u002F6.use-draggable","i-ph-hand-grabbing",{"title":75,"path":76,"stem":77,"icon":78},"useAnimeTimeline","\u002Fcomposables\u002Fuse-anime-timeline","2.composables\u002F7.use-anime-timeline","i-ph-list-numbers",{"title":80,"path":81,"stem":82,"icon":83},"useAnimeScroll","\u002Fcomposables\u002Fuse-anime-scroll","2.composables\u002F8.use-anime-scroll","i-ph-mouse-scroll",{"title":85,"path":86,"stem":87,"icon":88,"badge":89},"useAnimeLayout","\u002Fcomposables\u002Fuse-anime-layout","2.composables\u002F9.use-anime-layout","i-ph-layout","New",{"title":91,"path":92,"stem":93,"icon":94},"Utilities","\u002Fcomposables\u002Futilities","2.composables\u002F99.utilities","i-ph-puzzle-piece",{"title":96,"icon":6,"path":97,"stem":98,"children":99,"page":6},"Components","\u002Fcomponents","3.components",[100,105,110],{"title":101,"path":102,"stem":103,"icon":104,"badge":89},"Transitions","\u002Fcomponents\u002Ftransitions","3.components\u002F1.transitions","i-ph-arrows-left-right",{"title":106,"path":107,"stem":108,"icon":109,"badge":89},"Transition styles","\u002Fcomponents\u002Ftransition-styles","3.components\u002F2.transition-styles","i-ph-palette",{"title":111,"path":112,"stem":113,"icon":114,"badge":89},"Layout group","\u002Fcomponents\u002Flayout-group","3.components\u002F3.layout-group","i-ph-frame-corners",{"title":116,"icon":6,"path":117,"stem":118,"children":119,"page":6},"Comparisons","\u002Fcomparisons","4.comparisons",[120,125],{"title":121,"path":122,"stem":123,"icon":124,"badge":89},"Compared with Nuxt modules","\u002Fcomparisons\u002Fvs-nuxt-modules","4.comparisons\u002F1.vs-nuxt-modules","i-ph-scales",{"title":126,"path":127,"stem":128,"icon":124,"badge":89},"Compared with Vue packages","\u002Fcomparisons\u002Fvs-vue-packages","4.comparisons\u002F2.vs-vue-packages",{"title":130,"icon":6,"path":131,"stem":132,"children":133,"page":6},"Examples","\u002Fexamples","5.examples",[134,138,143,148],{"title":11,"path":135,"stem":136,"icon":137},"\u002Fexamples\u002Fintroduction","5.examples\u002F0.introduction","i-ph-shapes",{"title":139,"path":140,"stem":141,"icon":142},"Drive","\u002Fexamples\u002Fdrive","5.examples\u002F1.drive","i-ph-hard-drives",{"title":144,"path":145,"stem":146,"icon":147},"Road trip","\u002Fexamples\u002Froad-trip","5.examples\u002F2.road-trip","i-ph-car-profile",{"title":149,"path":150,"stem":151,"icon":152},"Player","\u002Fexamples\u002Fplayer","5.examples\u002F3.player","i-ph-music-notes",{"title":154,"icon":6,"path":155,"stem":156,"children":157,"page":6},"Updates","\u002Fchanges","6.changes",[158],{"title":159,"path":160,"stem":161,"icon":162},"Changelog","\u002Fchanges\u002Fchangelog","6.changes\u002F1.changelog","i-ph-clock-counter-clockwise",{"left":164,"top":165,"width":166,"height":167,"rotate":168,"vFlip":6,"hFlip":6,"body":169},84,74,344,367,0,"\u003Cpath d=\"M124 401 V230 C124 144 186 114 256 114 C326 114 388 144 388 230 V304\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"80\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u002F>\n  \u003Crect x=\"348\" y=\"358\" width=\"80\" height=\"80\" rx=\"12\" fill=\"#ff4b4b\"\u002F>",{"id":171,"title":149,"body":172,"description":485,"extension":486,"links":487,"meta":488,"navigation":489,"path":150,"seo":490,"stem":151,"__hash__":493},"docs\u002F5.examples\u002F3.player.md",{"type":173,"value":174,"toc":480},"minimark",[175,180,185,189,224,227,231,311,467],[176,177],"render-code-block-preview",{":code":178,"src":179},"false","examples\u002Fplayer\u002FPlayerDemo.vue",[181,182,184],"h2",{"id":183},"try-it","Try it",[186,187,188],"p",{},"The numbered controls work. The greyed ones fill out the page and do\nnothing. There is no audio: the playhead is an AnimeJS animation.",[190,191,192,200,206,212,218],"ol",{},[193,194,195,199],"li",{},[196,197,198],"strong",{},"Drag the seek bar."," The time follows the thumb while you drag, and\nplayback jumps there when you let go. While playing, the thumb moves\nwith the playhead.",[193,201,202,205],{},[196,203,204],{},"Play, pause, skip."," The play button's icon swaps with a small pop.\nThe level meters bounce while playing and settle when paused. On a new\ntrack the cover fades up, the title scrambles into the new name, then\nthe artist line fades in.",[193,207,208,211],{},[196,209,210],{},"Drag the volume slider."," The level meters get taller or shorter with\nthe volume.",[193,213,214,217],{},[196,215,216],{},"Click a track in the playlist"," to play it. The highlight moves to it,\nand the list stays where it is.",[193,219,220,223],{},[196,221,222],{},"Shuffle"," the playlist. The rows slide into their new order.",[186,225,226],{},"Reset in the title bar stops playback and puts the playlist back in order.",[181,228,230],{"id":229},"built-with","Built with",[232,233,234,243,250,257,264,271,279,287],"ul",{},[193,235,236,242],{},[237,238,239],"a",{"href":46},[240,241,45],"code",{},": the playhead, as an animation\nof a number",[193,244,245,249],{},[237,246,247],{"href":76},[240,248,75],{},": the artist line\nand total time on each track change",[193,251,252,256],{},[237,253,254],{"href":66},[240,255,65],{},": the track title",[193,258,259,263],{},[237,260,261],{"href":71},[240,262,70],{},": the seek and volume thumbs",[193,265,266,270],{},[237,267,268],{"href":56},[240,269,55],{},": the level meters",[193,272,273,278],{},[237,274,275],{"href":102},[240,276,277],{},"\u003CAnimeTransitionGroup>",": the playlist\nshuffle",[193,280,281,286],{},[237,282,283],{"href":102},[240,284,285],{},"\u003CAnimeTransition>",": the cover crossfade and\nthe play and pause icons",[193,288,289,292,293,298,299,302,303,292,306],{},[240,290,291],{},"spring"," from ",[237,294,295],{"href":92},[240,296,297],{},"#nanime\u002Feasings",", and ",[240,300,301],{},"shuffle","\nand ",[240,304,305],{},"random",[237,307,308],{"href":92},[240,309,310],{},"#nanime\u002Futils",[312,313,315,365,404,419,442,456],"collapsible",{"name":314},"How it's built",[186,316,317,320,321,324,325,328,329,333,334,337,338,341,342,302,345,348,349,352,353,356,357,360,361,364],{},[196,318,319],{},"Playhead."," There is no audio element. A ",[240,322,323],{},"usePlayer"," composable keeps\n",[240,326,327],{},"clock = reactive({ time: 0 })"," and animates it with\n",[237,330,331],{"href":46},[240,332,45],{},": ",[240,335,336],{},"time"," goes from ",[240,339,340],{},"0"," to the\ntrack's length in seconds, over that many seconds, with ",[240,343,344],{},"ease: 'linear'",[240,346,347],{},"autoplay: false",". Play and pause call the animation's ",[240,350,351],{},"play()"," and\n",[240,354,355],{},"pause()",". Seeking calls ",[240,358,359],{},"seek()"," with the time in milliseconds, which also\nworks while paused. ",[240,362,363],{},"onComplete"," moves to the next track. The params are a\ngetter that reads the current track, so a new track rebuilds the animation\nwith its own length.",[186,366,367,370,371,375,376,379,380,383,384,387,388,391,392,395,396,399,400,403],{},[196,368,369],{},"Sliders."," Both sliders are one component. The thumb gets\n",[237,372,373],{"href":71},[240,374,70],{}," with ",[240,377,378],{},"container"," set to the\nrail, ",[240,381,382],{},"y: false"," and ",[240,385,386],{},"velocityMultiplier: 0",", so it stays on the rail and\nstops where it is let go instead of sliding on. The rail is the thumb's\nwidth longer than the track it draws, so the thumb's centre lines up with\nboth ends. The value is the draggable's ",[240,389,390],{},"x"," divided by that range. While\nthe value changes from outside, for example as the playhead moves,\n",[240,393,394],{},"setX(value * range, true)"," moves the thumb without firing the drag\ncallbacks. ",[240,397,398],{},"onDrag"," reports the value as it moves and ",[240,401,402],{},"onRelease"," reports\nthe final one. The seek bar only seeks on release, and shows the dragged\ntime until then.",[186,405,406,409,410,414,415,418],{},[196,407,408],{},"Track change."," The cover is keyed on the track inside an\n",[237,411,412],{"href":102},[240,413,285],{},". Both covers sit\n",[240,416,417],{},"absolute inset-0"," in the same box, and the transition runs the old one's\nleave and the new one's enter together, so they crossfade over 400ms.",[186,420,421,425,426,429,430,434,435,437,438,441],{},[237,422,423],{"href":66},[240,424,65],{}," watches the track's\ntitle and scrambles the heading into each new one. The heading renders the\nfirst title with ",[240,427,428],{},"v-once",", so Vue leaves its text to the scramble after\nthat. The artist line and the total time keep their elements, so a\n",[237,431,432],{"href":76},[240,433,75],{}," built once with\n",[240,436,347],{}," holds their fades, and a watcher on the track's id calls\n",[240,439,440],{},"restart()",". They fade in at 150ms and 250ms, so a track change runs in\norder rather than all at once.",[186,443,444,447,448,452,453,455],{},[196,445,446],{},"Level meters."," Sixteen bars read their heights from one reactive object\nanimated by ",[237,449,450],{"href":56},[240,451,55],{},", one property\nper bar. While playing, an interval sets each bar to a new ",[240,454,305],{}," height\nevery 180ms, scaled by the volume. An animatable starts each move from\nwhere the bar is, so the bars never jump. When paused, every bar goes back\nto a low resting height.",[186,457,458,461,462,466],{},[196,459,460],{},"Playlist."," The playlist keeps all six tracks in place and marks the\ncurrent one, so picking a track only moves the highlight. A queue that\ntook the picked track out and put the old one back at the end would add a\nleave, a move and an enter to every track change. The list is an\n",[237,463,464],{"href":102},[240,465,277],{},", and Shuffle replaces\nit with a new order that the group moves the rows into over 350ms. The\nmove has no stagger. The group staggers every element inside it, not only\nthe rows, so a stagger would make each row's text trail behind its row.",[312,468,470],{"name":469},"AI build prompt",[471,472,478],"pre",{"className":473,"code":475,"language":476,"meta":477},[474],"language-text","Using the nanime Nuxt module (AnimeJS v4 for Vue, with Tailwind CSS v4 and\nNuxt UI), build a music player in Vue 3 single-file components, with no\nreal audio. useAnimate, useAnimeTimeline, useScrambleText, useDraggable,\nuseAnimatable, \u003CAnimeTransition> and \u003CAnimeTransitionGroup> are\nauto-imported in components. In a plain .ts composable, import useAnimate\nfrom '#nanime\u002Fcomposables'. Import spring from '#nanime\u002Feasings', and\nshuffle and random from '#nanime\u002Futils'. Use strict TypeScript with\nno `any` and no `as` casts. Ease names are AnimeJS v4 strings such as\n'out(3)'. Keep motion subtle: ease out for things already on screen, and a\nsmall spring overshoot only for things that pop in.\n\nData: six tracks { id, title, artist, album, duration (seconds, all\ndifferent), icon, cover (a gradient class per track) }. A usePlayer() composable holds playlist (all six\ntracks), currentId, playing, volume (0 to 1) and clock = reactive({ time:\n0 }). current is a computed that finds currentId in the playlist.\n\nPlayhead: playback = useAnimate(clock, () => ({ time: [0,\ncurrent.value.duration], duration: current.value.duration * 1000, ease:\n'linear', autoplay: false, onComplete: next })). The getter rebuilds the\nanimation for each new track. Watch currentId and call playback.seek(0),\nthen playback.play() if playing. play() and pause() set playing and call\nthe animation's play() and pause(). seek(fraction) calls\nplayback.seek(fraction * duration * 1000). next() and previous() step\ncurrentId through the playlist, wrapping at the ends, and previous()\nrestarts the track instead when more than 3 seconds in. playTrack(track)\nsets currentId and plays, or toggles play when it is already current.\nshufflePlaylist() replaces the playlist with shuffle([...playlist]). The\nplaylist never changes on a track change, only the highlight does.\n\nSlider component (used for seek and volume): props value (0 to 1) and label;\nemits input while dragging and change on release. Markup: a rail div\n\"relative h-4 w-full\" (template ref \"rail\"), a track \"absolute inset-x-2\ntop-1\u002F2 h-1\" with a fill whose width is the value in %, and a thumb\n\"absolute top-0 left-0 size-4 rounded-full touch-none\" (ref \"thumb\").\ndraggable = useDraggable(thumb, { container: rail, y: false,\nvelocityMultiplier: 0, onGrab, onDrag, onRelease, onResize: measure }).\nrange = rail width - thumb width, measured on mount and on resize. The value\nis draggable.x \u002F range, clamped to 0..1. While not dragging, watch the value\nprop and call draggable.setX(value * range, true), which moves the thumb\nwithout firing callbacks. While dragging, show the dragged value.\n\nNow playing: a cover, an \u003Ch2 ref=\"title\">, a meta line \"artist · album\",\nlevel meters, the seek slider with current and total time, previous \u002F\nplay-pause \u002F next buttons, the volume slider and a row of disabled buttons\n(like, repeat, lyrics, cast).\n- Cover: a \"relative size-40 overflow-hidden rounded-2xl\" box holding an\n  \u003CAnimeTransition> with enter { opacity: [0, 1], duration: 400, ease:\n  'inOut(2)' } and leave { opacity: 0, duration: 400, ease: 'inOut(2)' }.\n  Inside it, a div keyed on the track id, \"absolute inset-0\", with the\n  track's gradient and icon. Both covers are stacked during the change, so\n  they crossfade.\n- Title: render the first title with v-once so Vue never touches its text\n  again, and call useScrambleText(title, {}, () => ({ text: track.title,\n  settleDuration: 300, revealRate: 40 })). It scrambles into each new title.\n- Track change: trackChange = useAnimeTimeline({ autoplay: false, defaults:\n  { ease: 'out(3)', duration: 300 } }), then .add(meta, { opacity: [0, 1] },\n  150).add(total, { opacity: [0, 1] }, 250), where total is the total-time\n  label. Watch the track id and call trackChange.restart(). Things run in\n  order rather than all at once, since a track change happens often.\n- Seek: show the dragged time while dragging, and call seek only on change.\n- Play button: the icon inside \u003CAnimeTransition mode=\"out-in\"> keyed on\n  playing, enter { opacity: [0, 1], scale: [0.6, 1], ease: spring({ bounce:\n  0.2, duration: 250 }) }.\n\nLevel meters: 16 bars. levels = reactive(Record\u003Cstring, number>) with one\nkey per bar, levelsTo = useAnimatable(levels, one { duration: 180, ease:\n'out(2)' } per key). While playing, every 180ms call levelsTo[bar]?.(random(\n0.15, 1, 2) * (0.25 + 0.75 * volume)) for each bar. When paused, send each\nbar to 0.08. Draw each bar with transform: scaleY(level) and origin-bottom.\n\nPlaylist: all tracks as buttons inside \u003CAnimeTransitionGroup tag=\"ul\"\nclass=\"relative\" :move-animation=\"{ duration: 350, ease: 'out(3)' }\">\nkeyed by id. The current row gets a primary background and title colour,\nwith a colour transition. Clicking a row plays it. A Shuffle button\nshuffles the playlist, and the group moves the rows into the new order.\nGive the move no stagger: the group staggers every element inside it, so\na row's text would trail its row.\n","text","",[240,479,475],{"__ignoreMap":477},{"title":477,"searchDepth":481,"depth":481,"links":482},2,[483,484],{"id":183,"depth":481,"text":184},{"id":229,"depth":481,"text":230},"A music player with a timeline for track changes, draggable seek and volume sliders, and a playlist that shuffles.","md",null,{},{"icon":152},{"title":491,"description":492},"Music player example: timelines and sliders in Nuxt","A music player built with useAnimeTimeline, useDraggable, useAnimatable, useScrambleText and AnimeTransitionGroup in Nuxt. Track changes, seek and volume sliders, level meters and a playlist.","T30RwcoZu3_J8X0J8qmpdWp851iA5D1nMJtc_94w2Kw",[495,497],{"title":144,"path":145,"stem":146,"description":496,"icon":147,"children":-1},"A trip planner whose route is driven by scroll, with a booking log that runs step by step.",{"title":159,"path":160,"stem":161,"description":498,"icon":162,"children":-1},"What changed in each release, and what it means for your code.",{"data":500,"body":501,"toc":523},{"title":477,"description":477},{"type":502,"children":503},"root",[504,509],{"type":505,"tag":506,"props":507,"children":508},"element","player-demo",{},[],{"type":505,"tag":510,"props":511,"children":520},"u-button",{"className":512,"color":515,"icon":516,"target":517,"to":518,"variant":519},[513,514],"mt-2.5","pl-0.5","neutral","i-simple-icons-github","_blank","https:\u002F\u002Fgithub.com\u002Fastraldev\u002Fnanime\u002Fblob\u002Fmain\u002Fdocs\u002Fapp\u002Fcomponents\u002Fcontent\u002Fexamples\u002Fplayer\u002FPlayerDemo.vue","link",[521],{"type":476,"value":522},"View on GitHub",{"title":477,"searchDepth":481,"depth":481,"links":524},[],1792591718526]