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