[{"data":1,"prerenderedAt":595},["ShallowReactive",2],{"blog-gsap-vue3":3},{"id":4,"title":5,"body":6,"date":580,"description":581,"extension":582,"image":583,"isoDate":584,"meta":585,"navigation":92,"path":586,"readTime":587,"seo":588,"seoTitle":583,"stem":589,"tags":590,"__hash__":594},"content\u002Fblog\u002Fgsap-vue3.md","Integrating GSAP with Vue 3",{"type":7,"value":8,"toc":569},"minimark",[9,18,21,26,34,38,41,66,69,100,111,123,459,463,466,530,539,555,559,565],[10,11,12,13,17],"p",{},"Creating memorable web experiences often relies on the subtle art of animation. While CSS transitions are great for simple hover states, the ",[14,15,16],"strong",{},"GreenSock Animation Platform (GSAP)"," is the undisputed king of complex, timeline-based, and scroll-driven animations.",[10,19,20],{},"When paired with Vue 3's Composition API, GSAP becomes incredibly powerful. Here's a comprehensive guide to mastering this combination.",[22,23,25],"h2",{"id":24},"why-gsap","Why GSAP?",[10,27,28,29,33],{},"GSAP offers unmatched performance, cross-browser consistency, and an incredibly intuitive API. Its ",[30,31,32],"code",{},"ScrollTrigger"," plugin allows you to trigger animations precisely as elements enter or leave the viewport, pin elements, or scrub animations based on scroll position.",[22,35,37],{"id":36},"setup-in-nuxtvue-3","Setup in Nuxt\u002FVue 3",[10,39,40],{},"First, install the library:",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-bash shiki shiki-themes github-light github-dark","npm install gsap\n","bash","",[30,49,50],{"__ignoreMap":47},[51,52,55,59,63],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"sScJk","npm",[51,60,62],{"class":61},"sZZnC"," install",[51,64,65],{"class":61}," gsap\n",[10,67,68],{},"To avoid SSR issues in Nuxt, we often register the plugin globally on the client side, or specifically within our components.",[42,70,74],{"className":71,"code":72,"language":73,"meta":47,"style":47},"language-javascript shiki shiki-themes github-light github-dark","import { gsap } from 'gsap'\nimport { ScrollTrigger } from 'gsap\u002FScrollTrigger'\n\ngsap.registerPlugin(ScrollTrigger)\n","javascript",[30,75,76,81,87,94],{"__ignoreMap":47},[51,77,78],{"class":53,"line":54},[51,79,80],{},"import { gsap } from 'gsap'\n",[51,82,84],{"class":53,"line":83},2,[51,85,86],{},"import { ScrollTrigger } from 'gsap\u002FScrollTrigger'\n",[51,88,90],{"class":53,"line":89},3,[51,91,93],{"emptyLinePlaceholder":92},true,"\n",[51,95,97],{"class":53,"line":96},4,[51,98,99],{},"gsap.registerPlugin(ScrollTrigger)\n",[22,101,103,104,107,108],{"id":102},"the-vue-3-approach-onmounted-and-onunmounted","The Vue 3 Approach: ",[30,105,106],{},"onMounted"," and ",[30,109,110],{},"onUnmounted",[10,112,113,114,116,117,119,120,122],{},"Because GSAP manipulates the DOM directly, we must wait until the component is fully mounted. In Vue 3, we use the ",[30,115,106],{}," lifecycle hook. Crucially, to prevent memory leaks, especially with ",[30,118,32],{},", we must clean up in ",[30,121,110],{},".",[42,124,128],{"className":125,"code":126,"language":127,"meta":47,"style":47},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cdiv class=\"box-container\">\n    \u003Cdiv ref=\"boxRef\" class=\"box\">Animate Me\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport { gsap } from 'gsap'\n\nconst boxRef = ref(null)\nlet ctx;\n\nonMounted(() => {\n  \u002F\u002F Use gsap.context to easily clean up all animations created within it\n  ctx = gsap.context(() => {\n    gsap.to(boxRef.value, {\n      x: 200,\n      rotation: 360,\n      duration: 2,\n      ease: 'power3.out'\n    });\n  });\n})\n\nonUnmounted(() => {\n  ctx.revert(); \u002F\u002F This cleans up all GSAP animations and ScrollTriggers!\n})\n\u003C\u002Fscript>\n","vue",[30,129,130,143,162,191,200,210,215,228,244,257,262,286,295,300,314,321,341,353,365,376,387,396,402,408,414,419,430,445,450],{"__ignoreMap":47},[51,131,132,136,140],{"class":53,"line":54},[51,133,135],{"class":134},"sVt8B","\u003C",[51,137,139],{"class":138},"s9eBZ","template",[51,141,142],{"class":134},">\n",[51,144,145,148,151,154,157,160],{"class":53,"line":83},[51,146,147],{"class":134},"  \u003C",[51,149,150],{"class":138},"div",[51,152,153],{"class":57}," class",[51,155,156],{"class":134},"=",[51,158,159],{"class":61},"\"box-container\"",[51,161,142],{"class":134},[51,163,164,167,169,172,174,177,179,181,184,187,189],{"class":53,"line":89},[51,165,166],{"class":134},"    \u003C",[51,168,150],{"class":138},[51,170,171],{"class":57}," ref",[51,173,156],{"class":134},[51,175,176],{"class":61},"\"boxRef\"",[51,178,153],{"class":57},[51,180,156],{"class":134},[51,182,183],{"class":61},"\"box\"",[51,185,186],{"class":134},">Animate Me\u003C\u002F",[51,188,150],{"class":138},[51,190,142],{"class":134},[51,192,193,196,198],{"class":53,"line":96},[51,194,195],{"class":134},"  \u003C\u002F",[51,197,150],{"class":138},[51,199,142],{"class":134},[51,201,203,206,208],{"class":53,"line":202},5,[51,204,205],{"class":134},"\u003C\u002F",[51,207,139],{"class":138},[51,209,142],{"class":134},[51,211,213],{"class":53,"line":212},6,[51,214,93],{"emptyLinePlaceholder":92},[51,216,218,220,223,226],{"class":53,"line":217},7,[51,219,135],{"class":134},[51,221,222],{"class":138},"script",[51,224,225],{"class":57}," setup",[51,227,142],{"class":134},[51,229,231,235,238,241],{"class":53,"line":230},8,[51,232,234],{"class":233},"szBVR","import",[51,236,237],{"class":134}," { ref, onMounted, onUnmounted } ",[51,239,240],{"class":233},"from",[51,242,243],{"class":61}," 'vue'\n",[51,245,247,249,252,254],{"class":53,"line":246},9,[51,248,234],{"class":233},[51,250,251],{"class":134}," { gsap } ",[51,253,240],{"class":233},[51,255,256],{"class":61}," 'gsap'\n",[51,258,260],{"class":53,"line":259},10,[51,261,93],{"emptyLinePlaceholder":92},[51,263,265,268,272,275,277,280,283],{"class":53,"line":264},11,[51,266,267],{"class":233},"const",[51,269,271],{"class":270},"sj4cs"," boxRef",[51,273,274],{"class":233}," =",[51,276,171],{"class":57},[51,278,279],{"class":134},"(",[51,281,282],{"class":270},"null",[51,284,285],{"class":134},")\n",[51,287,289,292],{"class":53,"line":288},12,[51,290,291],{"class":233},"let",[51,293,294],{"class":134}," ctx;\n",[51,296,298],{"class":53,"line":297},13,[51,299,93],{"emptyLinePlaceholder":92},[51,301,303,305,308,311],{"class":53,"line":302},14,[51,304,106],{"class":57},[51,306,307],{"class":134},"(() ",[51,309,310],{"class":233},"=>",[51,312,313],{"class":134}," {\n",[51,315,317],{"class":53,"line":316},15,[51,318,320],{"class":319},"sJ8bj","  \u002F\u002F Use gsap.context to easily clean up all animations created within it\n",[51,322,324,327,329,332,335,337,339],{"class":53,"line":323},16,[51,325,326],{"class":134},"  ctx ",[51,328,156],{"class":233},[51,330,331],{"class":134}," gsap.",[51,333,334],{"class":57},"context",[51,336,307],{"class":134},[51,338,310],{"class":233},[51,340,313],{"class":134},[51,342,344,347,350],{"class":53,"line":343},17,[51,345,346],{"class":134},"    gsap.",[51,348,349],{"class":57},"to",[51,351,352],{"class":134},"(boxRef.value, {\n",[51,354,356,359,362],{"class":53,"line":355},18,[51,357,358],{"class":134},"      x: ",[51,360,361],{"class":270},"200",[51,363,364],{"class":134},",\n",[51,366,368,371,374],{"class":53,"line":367},19,[51,369,370],{"class":134},"      rotation: ",[51,372,373],{"class":270},"360",[51,375,364],{"class":134},[51,377,379,382,385],{"class":53,"line":378},20,[51,380,381],{"class":134},"      duration: ",[51,383,384],{"class":270},"2",[51,386,364],{"class":134},[51,388,390,393],{"class":53,"line":389},21,[51,391,392],{"class":134},"      ease: ",[51,394,395],{"class":61},"'power3.out'\n",[51,397,399],{"class":53,"line":398},22,[51,400,401],{"class":134},"    });\n",[51,403,405],{"class":53,"line":404},23,[51,406,407],{"class":134},"  });\n",[51,409,411],{"class":53,"line":410},24,[51,412,413],{"class":134},"})\n",[51,415,417],{"class":53,"line":416},25,[51,418,93],{"emptyLinePlaceholder":92},[51,420,422,424,426,428],{"class":53,"line":421},26,[51,423,110],{"class":57},[51,425,307],{"class":134},[51,427,310],{"class":233},[51,429,313],{"class":134},[51,431,433,436,439,442],{"class":53,"line":432},27,[51,434,435],{"class":134},"  ctx.",[51,437,438],{"class":57},"revert",[51,440,441],{"class":134},"(); ",[51,443,444],{"class":319},"\u002F\u002F This cleans up all GSAP animations and ScrollTriggers!\n",[51,446,448],{"class":53,"line":447},28,[51,449,413],{"class":134},[51,451,453,455,457],{"class":53,"line":452},29,[51,454,205],{"class":134},[51,456,222],{"class":138},[51,458,142],{"class":134},[22,460,462],{"id":461},"mastering-scrolltrigger","Mastering ScrollTrigger",[10,464,465],{},"ScrollTrigger is where the magic happens. Let's create an animation that fires when an element enters the screen.",[42,467,469],{"className":71,"code":468,"language":73,"meta":47,"style":47},"gsap.from('.stagger-item', {\n  scrollTrigger: {\n    trigger: '.stagger-container',\n    start: 'top 80%', \u002F\u002F When the top of the container hits 80% down the viewport\n    end: 'bottom 20%',\n    toggleActions: 'play none none reverse', \u002F\u002F play on enter, reverse on leave back\n  },\n  y: 50,\n  opacity: 0,\n  duration: 0.8,\n  stagger: 0.2\n})\n",[30,470,471,476,481,486,491,496,501,506,511,516,521,526],{"__ignoreMap":47},[51,472,473],{"class":53,"line":54},[51,474,475],{},"gsap.from('.stagger-item', {\n",[51,477,478],{"class":53,"line":83},[51,479,480],{},"  scrollTrigger: {\n",[51,482,483],{"class":53,"line":89},[51,484,485],{},"    trigger: '.stagger-container',\n",[51,487,488],{"class":53,"line":96},[51,489,490],{},"    start: 'top 80%', \u002F\u002F When the top of the container hits 80% down the viewport\n",[51,492,493],{"class":53,"line":202},[51,494,495],{},"    end: 'bottom 20%',\n",[51,497,498],{"class":53,"line":212},[51,499,500],{},"    toggleActions: 'play none none reverse', \u002F\u002F play on enter, reverse on leave back\n",[51,502,503],{"class":53,"line":217},[51,504,505],{},"  },\n",[51,507,508],{"class":53,"line":230},[51,509,510],{},"  y: 50,\n",[51,512,513],{"class":53,"line":246},[51,514,515],{},"  opacity: 0,\n",[51,517,518],{"class":53,"line":259},[51,519,520],{},"  duration: 0.8,\n",[51,522,523],{"class":53,"line":264},[51,524,525],{},"  stagger: 0.2\n",[51,527,528],{"class":53,"line":288},[51,529,413],{},[531,532,534,535,538],"h3",{"id":533},"pro-tip-gsapcontext-is-your-best-friend","Pro Tip: ",[30,536,537],{},"gsap.context()"," is your Best Friend",[10,540,541,542,544,545,547,548,550,551,554],{},"In modern Vue\u002FReact development, cleaning up ",[30,543,32],{}," instances used to be tedious. The introduction of ",[30,546,537],{}," solves this. Any animation or ScrollTrigger created inside a ",[30,549,334],{}," can be instantly killed by calling ",[30,552,553],{},".revert()"," on that context when the component unmounts.",[22,556,558],{"id":557},"conclusion","Conclusion",[10,560,561,562,564],{},"GSAP and Vue 3 are a match made in heaven for creative developers. By understanding the lifecycle hooks and utilizing ",[30,563,537],{},", you can build incredibly complex, high-performance animations that are completely free of memory leaks.",[566,567,568],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":47,"searchDepth":83,"depth":83,"links":570},[571,572,573,575,579],{"id":24,"depth":83,"text":25},{"id":36,"depth":83,"text":37},{"id":102,"depth":83,"text":574},"The Vue 3 Approach: onMounted and onUnmounted",{"id":461,"depth":83,"text":462,"children":576},[577],{"id":533,"depth":89,"text":578},"Pro Tip: gsap.context() is your Best Friend",{"id":557,"depth":83,"text":558},"Aug 05, 2024","A comprehensive guide to building complex, high-performance scroll animations in Vue 3 with GSAP and ScrollTrigger, including SSR-safe setup and cleanup.","md",null,"2024-08-05",{},"\u002Fblog\u002Fgsap-vue3","6 min read",{"title":5,"description":581},"blog\u002Fgsap-vue3",[591,592,593],"GSAP","Vue","Animation","JCxGsUYee_CopzVmGRPoEkvJpMbhleXZO8JoGJR0Vjo",1785350182148]