[{"data":1,"prerenderedAt":398},["ShallowReactive",2],{"navigation_docs":3,"-examples-text-scatter":137,"-examples-text-scatter-surround":368,"preview-examples\u002Fshowcase\u002Ftext-scatter\u002FTextScatterDemo.vue-false":373,"mdc-fyrzpw-key":374},[4,30,74,98,127],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":6},"Getting Started",false,"\u002Fgetting-started","1.getting-started",[10,15,20,25],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Configuration","\u002Fgetting-started\u002Fconfiguration","1.getting-started\u002F3.configuration","i-lucide-settings",{"title":26,"path":27,"stem":28,"icon":29},"Performance","\u002Fgetting-started\u002Fperformance","1.getting-started\u002F4.performance","i-lucide-gauge",{"title":31,"icon":6,"path":32,"stem":33,"children":34,"page":6},"Composables","\u002Fcomposables","2.composables",[35,39,44,49,54,59,64,69],{"title":11,"path":36,"stem":37,"icon":38},"\u002Fcomposables\u002Fintroduction","2.composables\u002F0.introduction","i-lucide-box",{"title":40,"path":41,"stem":42,"icon":43},"useAnimate","\u002Fcomposables\u002Fuse-animate","2.composables\u002F1.use-animate","i-ph-play-circle",{"title":45,"path":46,"stem":47,"icon":48},"useWaapiAnimate","\u002Fcomposables\u002Fuse-waapi-animate","2.composables\u002F2.use-waapi-animate","i-ph-lightning",{"title":50,"path":51,"stem":52,"icon":53},"useAnimatable","\u002Fcomposables\u002Fuse-animatable","2.composables\u002F3.use-animatable","i-ph-cube",{"title":55,"path":56,"stem":57,"icon":58},"useSplitText","\u002Fcomposables\u002Fuse-split-text","2.composables\u002F4.use-split-text","i-ph-text-t",{"title":60,"path":61,"stem":62,"icon":63},"useScrambleText","\u002Fcomposables\u002Fuse-scramble-text","2.composables\u002F5.use-scramble-text","i-ph-shuffle",{"title":65,"path":66,"stem":67,"icon":68},"useDraggable","\u002Fcomposables\u002Fuse-draggable","2.composables\u002F6.use-draggable","i-ph-hand-grabbing",{"title":70,"path":71,"stem":72,"icon":73},"useAnimeTimeline","\u002Fcomposables\u002Fuse-anime-timeline","2.composables\u002F7.use-anime-timeline","i-ph-list-numbers",{"title":75,"path":76,"stem":77,"children":78,"page":6},"Misc","\u002Fmisc","4.misc",[79,83,88,93],{"title":11,"path":80,"stem":81,"icon":82},"\u002Fmisc\u002Fintroduction","4.misc\u002F0.introduction","i-ph-puzzle-piece",{"title":84,"path":85,"stem":86,"icon":87},"Easings","\u002Fmisc\u002Feasings","4.misc\u002F1.easings","i-ph-chart-line",{"title":89,"path":90,"stem":91,"icon":92},"Utils","\u002Fmisc\u002Futils","4.misc\u002F2.utils","i-ph-wrench",{"title":94,"path":95,"stem":96,"icon":97},"Proxies","\u002Fmisc\u002Fproxies","4.misc\u002F3.proxies","i-ph-plugs-connected",{"title":99,"icon":6,"path":100,"stem":101,"children":102,"page":6},"Examples","\u002Fexamples","5.examples",[103,107,112,117,122],{"title":11,"path":104,"stem":105,"icon":106},"\u002Fexamples\u002Fintroduction","5.examples\u002F0.introduction","i-ph-shapes",{"title":108,"path":109,"stem":110,"icon":111},"Timeline storyboard","\u002Fexamples\u002Ftimeline-storyboard","5.examples\u002F1.timeline-storyboard","i-ph-film-strip",{"title":113,"path":114,"stem":115,"icon":116},"Drag to bin","\u002Fexamples\u002Fdrag-to-bin","5.examples\u002F2.drag-to-bin","i-ph-trash",{"title":118,"path":119,"stem":120,"icon":121},"Loading sequence","\u002Fexamples\u002Floading-sequence","5.examples\u002F3.loading-sequence","i-ph-spinner-gap",{"title":123,"path":124,"stem":125,"icon":126},"Text magnet","\u002Fexamples\u002Ftext-scatter","5.examples\u002F4.text-scatter","i-ph-magnet",{"title":128,"icon":6,"path":129,"stem":130,"children":131,"page":6},"Updates","\u002Fchanges","6.changes",[132],{"title":133,"path":134,"stem":135,"icon":136},"Changelog","\u002Fchanges\u002Fchangelog","6.changes\u002F1.changelog","i-ph-clock-counter-clockwise",{"id":138,"title":123,"body":139,"description":361,"extension":362,"links":363,"meta":364,"navigation":365,"path":124,"seo":366,"stem":125,"__hash__":367},"docs\u002F5.examples\u002F4.text-scatter.md",{"type":140,"value":141,"toc":355},"minimark",[142,146,151,156,159,163,176,183,190,233,240,243,247,341],[143,144,145],"p",{},"Move the cursor over the paragraph and nearby characters react like iron\nfilings near a magnet, pushed away and back into the page, then eased back\nto their resting spot. Every five seconds the polarity flips, so what was\nrepelling starts attracting and vice versa. The cursor doesn't need to move\nfor the change to show.",[147,148],"render-code-block-preview",{":code":149,"src":150},"false","examples\u002Fshowcase\u002Ftext-scatter\u002FTextScatterDemo.vue",[152,153,155],"h2",{"id":154},"whats-happening","What's happening",[143,157,158],{},"Every letter in the paragraph is its own little element, and each one only\nreacts once the cursor gets close enough. When it does, the letter gets\npushed away (or pulled in, depending on the mode) with a bit of random\njitter added, so a row of letters doesn't move in a stiff, identical line.\nRather than sliding flat, it also tumbles and recedes into the page, like\nit's being knocked backward away from the cursor. Once it's drifted, it\neases back to where it started, at its original size and depth.",[152,160,162],{"id":161},"how-its-built","How it's built",[143,164,165,171,172,175],{},[166,167,168],"a",{"href":56},[169,170,55],"code",{}," splits the paragraph into one\nelement per character first, since each one needs to move on its own. A\ncontinuous timer, not the ",[169,173,174],{},"pointermove"," event, drives the distance check, so\nit keeps running even while the cursor sits still. That matters since the\nmode flips on its own 5-second timer whether or not the pointer is moving.",[143,177,178,179,182],{},"On every tick, it walks every character and skips two kinds: anything\noutside the radius, and anything whose current animation is less than 40%\ncomplete (",[169,180,181],{},"progress \u003C 0.4","). That second rule stops a held-still cursor from\nrestarting the same letter's animation on every single tick. A new push can\nonly interrupt one that's already more than 40% through, not one that just\nstarted.",[143,184,185,186,189],{},"Each push is a two-step animation: an immediate displacement away from or\ntoward the pointer, then a return to the character's original spot on\n",[169,187,188],{},"inOutQuad",", held back by a 400ms delay and eased over 800ms. Because that\nreturn step always targets the same rest position, a character never drifts\nno matter how many times it gets pushed again mid-settle.",[143,191,192,193,196,197,200,201,204,205,208,209,212,213,216,217,220,221,224,225,228,229,232],{},"The depth comes from adding ",[169,194,195],{},"z"," (Anime.js's shorthand for ",[169,198,199],{},"translateZ",")\nalongside ",[169,202,203],{},"x"," and ",[169,206,207],{},"y",", pushed to a negative value so the character moves\naway from the viewer, plus ",[169,210,211],{},"rotateX","\u002F",[169,214,215],{},"rotateY"," for the tumble. All three\nscale off the same distance-based ",[169,218,219],{},"force"," value already used for the push\nitself, and all ease back to ",[169,222,223],{},"0"," in the same return keyframe. None of this\nreads as depth without a tight CSS ",[169,226,227],{},"perspective"," on the container. A large\nperspective value flattens the effect toward a 2D skew. A smaller one (this\ndemo uses ",[169,230,231],{},"400px",") puts the viewer closer to the scene and exaggerates it.",[143,234,235,236,239],{},"Character positions are measured once with ",[169,237,238],{},"getBoundingClientRect()"," after\nthe split resolves, then re-measured only on resize, on scroll and when the\npointer enters the container, since a pointer move alone can't change the\nlayout.",[143,241,242],{},"The halo that marks the radius is separate from all of this. The hit test\nruns off plain, non-reactive pointer variables, while the halo is positioned\nfrom container-relative refs, so the reactive work is limited to the one\nelement that needs to re-render.",[152,244,246],{"id":245},"building-it-yourself","Building it yourself",[248,249,250,262,272,281,291,334],"ol",{},[251,252,253,254,258,261],"li",{},"Split the paragraph into characters with ",[166,255,256],{"href":56},[169,257,55],{},[169,259,260],{},"(el, { chars: true })",".",[251,263,264,265,267,268,271],{},"Measure each character's ",[169,266,238],{}," once after the split resolves, and again on resize, scroll and ",[169,269,270],{},"pointerenter",", never on pointer move.",[251,273,274,275,204,277,280],{},"Track the pointer coordinates used for hit testing in plain variables on ",[169,276,174],{},[169,278,279],{},"pointerleave",", since nothing re-renders from them. Keep separate container-relative refs for the radius halo, which does.",[251,282,283,284,287,288,290],{},"Run a continuous ",[169,285,286],{},"createTimer({ onUpdate })"," loop rather than reacting to ",[169,289,174],{}," directly, so the effect keeps evaluating even while the pointer holds still.",[251,292,293,294,297,298,300,301,303,304,212,306,308,309,311,312,311,314,311,316,311,319,321,322,324,325,327,328,330,331,333],{},"In ",[169,295,296],{},"onUpdate",", for each character: skip it if it's outside a fixed radius from the pointer, skip it if its current animation's progress is less than 40% (",[169,299,181],{},"), otherwise animate it with a two-keyframe motion. The first keyframe displaces it along the line to the pointer, pushes ",[169,302,195],{}," negative so it recedes into the page, and tilts it with ",[169,305,211],{},[169,307,215],{},", all scaled by the same distance-based force. The second keyframe eases ",[169,310,203],{},", ",[169,313,207],{},[169,315,195],{},[169,317,318],{},"rotate",[169,320,211],{},", and ",[169,323,215],{}," all back to ",[169,326,223],{}," on ",[169,329,188],{},", after a 400ms delay and over 800ms. Give the container a tight CSS ",[169,332,227],{}," (a few hundred pixels) so the depth reads.",[251,335,336,337,340],{},"Flip a repel and attract mode ref on a ",[169,338,339],{},"useIntervalFn"," every 5 seconds. Nothing else needs to change, since the push vector's sign already reads from that ref.",[342,343,345],"collapsible",{"name":344},"AI build prompt",[346,347,353],"pre",{"className":348,"code":350,"language":351,"meta":352},[349],"language-text","Using the nanime Nuxt module, build a Vue 3 component with a paragraph of\ntext that reacts to cursor proximity like a magnet. Split the paragraph\ninto character elements with useSplitText(el, { chars: true }).\n\nTrack pointer position on pointermove and pointerleave over a container.\nRun a continuous timer with createTimer({ onUpdate }) that, on each tick,\nwalks every split character and, for any character within a fixed pixel\nradius of the pointer, animates it with a two-keyframe animation: the first\nkeyframe displaces it away from (repel mode) or toward (attract mode) the\npointer along the vector between them, scaled by how close the pointer is.\nThe first keyframe also pushes z (Anime.js's shorthand for translateZ) to a\nnegative value so the character recedes away from the viewer, and tilts it\nwith rotateX and rotateY, all scaled by the same distance-based force used\nfor the push. The second keyframe eases x, y, z, rotate, rotateX, and\nrotateY all back to 0 with ease: 'inOutQuad', delay: 400 and duration: 800.\nUse Anime.js's own easing names like 'inOutQuad', not the 'easeInOutQuad'\nconvention other libraries use, since an unrecognized ease string fails\nsilently and falls back to linear. Give the container a tight CSS perspective (a few hundred pixels,\nnot a thousand-plus) so the depth reads instead of flattening\ninto a skew. Skip characters whose current animation is less than 40%\ncomplete (progress \u003C 0.4), so a new push can only interrupt one that has\nalready progressed past that point, not one that started a frame ago.\n\nToggle a repel and attract mode ref automatically every 5 seconds with\nuseIntervalFn. Measure character positions with getBoundingClientRect()\nonce after the split resolves, and re-measure only on window resize and\nscroll.\n","text","",[169,354,350],{"__ignoreMap":352},{"title":352,"searchDepth":356,"depth":356,"links":357},2,[358,359,360],{"id":154,"depth":356,"text":155},{"id":161,"depth":356,"text":162},{"id":245,"depth":356,"text":246},"Flipping magnetic text attraction and repulsion.","md",null,{},{"icon":126},{"title":123,"description":361},"gaDbyc1WcHRHMl50YT7LVg_qSi9xQasbRyqeTpnFZLE",[369,371],{"title":118,"path":119,"stem":120,"description":370,"icon":121,"children":-1},"Idle to loading to success.",{"title":133,"path":134,"stem":135,"description":372,"icon":136,"children":-1},"Notable changes in nanime.","\n::text-scatter-demo\n::\n\n\n::u-button\n---\nto: https:\u002F\u002Fgithub.com\u002Fastraldev\u002Fnanime\u002Fblob\u002Fmain\u002Fdocs\u002Fapp\u002Fcomponents\u002Fcontent\u002Fexamples\u002Fshowcase\u002Ftext-scatter\u002FTextScatterDemo.vue\ntarget: _blank\nvariant: link\ncolor: neutral\nicon: i-simple-icons-github\nclass: mt-2.5 pl-0.5\n---\nView on GitHub\n::\n",{"data":375,"body":376},{},{"type":377,"children":378},"root",[379,384],{"type":380,"tag":381,"props":382,"children":383},"element","text-scatter-demo",{},[],{"type":380,"tag":385,"props":386,"children":395},"u-button",{"className":387,"color":390,"icon":391,"target":392,"to":393,"variant":394},[388,389],"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\u002Fshowcase\u002Ftext-scatter\u002FTextScatterDemo.vue","link",[396],{"type":351,"value":397},"View on GitHub",1788821081843]