pxel Posted July 13, 2020 Share Posted July 13, 2020 Hi, I have a question regarding using ScrollTrigger.matchMedia in multiple places within a web app (which will include using the same breakpoints in multiple places) To paint the scenario for you, so you have some more context. I have a Nuxt.js web app, that is using ScrollTrigger for various animations, which are setup within individual components throughout the app, which allows me to only create / destroy ScrollTrigger and gsap instances where needed to keep things nice and tidy. I noticed in the video tutorial for ScrollTrigger.matchMedia, that declaring this object once seems to be the recommended way, and then using the media queries as keys - e.g '(min-width: 800px)' pointing to a function which would handle ALL the ScrollTrigger instances for each breakpoint. My question is, is there a specific way that I should be using ScrollTrigger.matchMedia() within a component, and setting up the gsap / scrollTrigger animations only related to that component. I have been playing around with this for the last few hours, and I keep running up against issues, as I am presumably using it incorrectly. UPDATE: Small Codepen example in the below reply. Apologies in advance as I haven't included any specific code in this post. I am just seeing if there is a simple way of achieving this with ScrollTrigger.matchMedia, or if I need to setup something a bit more custom to acheive this. If its better for me to setup a small repo / codesandbox with a simple example showing exactly what I mean, let me know and I will reply to this post with it P.S Just wanted to say that ScrollTrigger is the absolute bomb, and I've been using it since the day it launched! Thanks in advance! Link to comment Share on other sites More sharing options...
pxel Posted July 13, 2020 Author Share Posted July 13, 2020 See the Pen BajPRBQ by pxel (@pxel) on CodePen Here's a quick pen I did up to show you what I mean. I feel like this is incorrect usage of the ScrollTrigger.matchMedia() method, but this is essentially the result I am after... Being able to run multiple ScrollTrigger.matchMedia methods on 1 page (based on whichever components are loaded into a page) I noticed on initial load Component 2's animation won't work, but resizing the window down (below 900) and back up again seems to make Component 2's animation work. Thanks again! Link to comment Share on other sites More sharing options...
GreenSock Posted July 13, 2020 Share Posted July 13, 2020 Yeah, you just do one matchMedia() call and put all your code in there. See the Pen 0447e38edce9567cb2e1151a49957dae?editors=0010 by GreenSock (@GreenSock) on CodePen The problem with the way you're doing it is that there's one function per key, and you're basically duplicating keys. In other words, '(min-width: 900px)' is already associated with a setup function. I never even thought of someone trying to duplicate things like that. If there's enough demand for it, we could probably add more code to ScrollTrigger to accommodate that. 2 Link to comment Share on other sites More sharing options...
pxel Posted July 13, 2020 Author Share Posted July 13, 2020 Hey Jack - Thank you for your reply! Ok, in saying that is there a way you would recommend implementing matchMedia() on a Single Page App (with javascript based routing) like Nuxt.js for example? I can get my head around having one matchMedia() call, what I am trying to get my head around is having the contents within each breakpoint be dynamic or interchangable, or some kind of lifecycle where I can destroy and recreate it between route changes. For example: a )I setup matchMedia() (Global component) b) My header may utilise ScrollTrigger, where matchMedia() is required (Global component) c) My home page may have 3 'sections' utilising ScrollTrigger, where matchMedia() is required (Page component) d) My about page may have 2 different 'sections' utilising ScrollTrigger, where matchMedia() is required (Page component) So that means, on the home page matchMedia() would include animations for my header, and home page x 3 sections. If i navigate to the about page, I would want to (destroy & rebuild / reinitialise) matchMedia() to include animations for my header, and about page x 2 sections. I hope that makes sense! If you think matchMedia() is the wrong tool for the job here, and something custom be required, that's totally understandable Thanks again for the help, I really appreciate it! Link to comment Share on other sites More sharing options...
pxel Posted July 13, 2020 Author Share Posted July 13, 2020 For anyone interested, I found a solution for this that seems to work quite well. Here is the code from my vue component that acts like a basic event bus, I emit 'animations' that I would like to have be responsive, and then dynamically build out the matchMedia() method / refresh scrollTrigger whenever there are new animations detected. Note that is is code from Nuxt JS / Vue JS: Code for the component that is listening for the emitted animations: // code snippet from '~/components/ScrollTriggerMatchMedia.vue' component export default { data() { return { animations: [] } }, created() { // listen for global $emits for new animations this.$nuxt.$on('push-animations', (animations) => { this.addAnimations(animations) }) }, methods: { // build ScrollTrigger.matchMedia method rebuildAnimations() { let animations = this.animations // get unique key values - e.g: '(max-width: 900px)', 'all', etc let breaks = [...new Set(animations.map(a => a.bp))] let breakpoints = {} // build out the object for matchMedia with each unique breakpoint as a key breaks.forEach(b => { breakpoints[b] = () => { // loop through all animations with the same breakpoint animations.filter(a => a.bp === b).forEach((fa, i) => { fa['animation']() }) } }) // run the ScrollTrigger.matchMedia() method, with our breakpoints object this.$ScrollTrigger.matchMedia(breakpoints) // refresh ScrollTrigger (using safe mode) this.$ScrollTrigger.refresh(true) }, // push animations to component data addAnimations(animations) { this.animations.push(...animations) } }, watch: { // rebuild triggered when there are any changes to the animations array inside the component's data animations() { this.rebuildAnimations() } } } Code for the component that is emitting the animation(s): // code snippet from '~/components/Header.vue' component export default { mounted() { this.$nuxt.$emit('push-animations', [ { // this.gsapAnimation() returns a gsap.timeline animation: () => { this.gsapAnimation() }, // breakpoint to be grouped into inside ScrollTrigger.matchMedia() bp: '(min-width: 900px)' }, { // can also inline gsap timelines here animation: () => { let tl = this.$gsap.timeline({ scrollTrigger: { id: 'scrollTriggerHeader', //trigger: this.$refs['header'], scrub: 0.2, start: 'top top-=30px', end: 'top top-=100px', } }) tl.to(this.$refs['logo'], { y: -90, duration: 0.6, ease: "expo.inOut" }) }, bp: '(min-width: 900px)' }, ]) }, destroyed() { // used to kill the ScrollTrigger instance for this component this.$ScrollTrigger.getById('scrollTriggerHeader').kill() } } Some notes here, my setup has a custom plugin that initialises gsap and ScrollTrigger, and binds them to the global vue instance within nuxt. This is how I did that: // code snippet from '~/plugins/gsap.js' import Vue from 'vue' import gsap from 'gsap' import ScrollToPlugin from 'gsap/ScrollToPlugin' import ScrollTrigger from 'gsap/ScrollTrigger' gsap.registerPlugin(ScrollToPlugin) gsap.registerPlugin(ScrollTrigger) const GSAP = { install (Vue, options) { Vue.prototype.$gsap = gsap Vue.prototype.$ScrollTrigger = ScrollTrigger } } Vue.use(GSAP) I hope this can help someone else on their adventures with using ScrollTrigger in a Javascript framework 1 Link to comment Share on other sites More sharing options...
GreenSock Posted July 14, 2020 Share Posted July 14, 2020 Thanks for sharing your solution! I rolled up my sleeves and worked on accommodating a use case like this, so you can add the same breakpoint multiple times and it should work in 3.4.1. Here's a preview: https://assets.codepen.io/16327/ScrollTrigger.min.js Furthermore, you can optionally return a function that'll be called when the breakpoint is no longer active (teardown). I figured you might appreciate that. Better? 6 Link to comment Share on other sites More sharing options...
pxel Posted July 14, 2020 Author Share Posted July 14, 2020 8 minutes ago, GreenSock said: Thanks for sharing your solution! I rolled up my sleeves and worked on accommodating a use case like this, so you can add the same breakpoint multiple times and it should work in 3.4.1. Here's a preview: https://assets.codepen.io/16327/ScrollTrigger.min.js Furthermore, you can optionally return a function that'll be called when the breakpoint is no longer active (teardown). I figured you might appreciate that. Better? Awesome! I like the sound of that 🙌 😁 Thanks so much, you're a true Superhero! 1 Link to comment Share on other sites More sharing options...
emjay Posted July 23, 2020 Share Posted July 23, 2020 Hi @GreenSock and @pxel, I found this thread using the search and think the solution would help in my case. I tried the following with ScrollTrigger.matchMedia() : ScrollTrigger.matchMedia({ "(min-width: 1025px) and (min-aspect-ratio: 4/3) and (max-aspect-ratio: 2/1)": function() { document.documentElement.style.margin = 0; document.documentElement.style.height = "100%"; document.body.classList.add("gsap"); let tl = gsap.timeline({ scrollTrigger: { trigger: "#container", start: "top top", end: "+=4000", scrub: true, pin: true, anticipatePin: 1 } }); let slides = gsap.utils.toArray(".imageText"); slides.shift(); // remove first slide, because it should be the start slide slides.forEach((slide, i) => { tl.from(slide, { xPercent: 100, scale: 0.9 }); }); }, "(max-width: 1024px)": function() { document.body.classList.remove("gsap"); document.documentElement.style.margin = 0; document.documentElement.style.height = "auto"; }, }); What I'm trying to do is based on Greensocks Sliding Panels demo, but the timeline and animations are working, thats not the problem, I have a question regarding the breakpoints. As you can see in my example I try to set a certain aspect ratio where scrollTrigger is active. Since the sliding panels always have 100% viewport height and I want to make sure that the content always fits into the viewport I use the aspect ratio in my media query and assign the needed styling for the slide in panels. Otherwise the panels are normally arranged one below the other. The reason why I give the body the class gsap is that I control the styling of the elements with this class. The two lines below, I'm adding the styles needed for the html element. Now I want to delete the gsap class and the html stylings if this aspect ratio breakpoint isn't matched anymore. And I do not know how I could do that. The All Key does not help me here, but maybe the teardown function? Thanks in advance for your reply and this amazing set of tools. Link to comment Share on other sites More sharing options...
ZachSaucier Posted July 23, 2020 Share Posted July 23, 2020 2 hours ago, emjay said: Now I want to delete the gsap class and the html stylings if this aspect ratio breakpoint isn't matched anymore. And I do not know how I could do that. The above code works. What's your question? See the Pen MWKxwyg?editors=0010 by GreenSock (@GreenSock) on CodePen Link to comment Share on other sites More sharing options...
emjay Posted July 23, 2020 Share Posted July 23, 2020 Hey @ZachSaucier The problem is, we have 3 "states" but only two matching breakpoints. I need a way, to tell scrollTrigger the third state (if both breakpoints don't match: State 1 ( all until 1024px viewport width) : "(max-width: 1024px)" State 2 (all up 1025px with a aspect ratio between 4/3 and 2/1) : "(min-width: 1025px) and (min-aspect-ratio: 4/3) and (max-aspect-ratio: 2/1)" Missing: State 3 (all up 1025px where the aspect ratio is not betweend 4/3 and 2/1) Something like this (dummy Code): ScrollTrigger.matchMedia({ "(min-width: 1025px) and (min-aspect-ratio: 4/3) and (max-aspect-ratio: 2/1)": function() { }, "not-matching": function() { // if no breakpoint is matching }, }); In this Case, I also wouldn't need State 2. Thanks @ZachSaucier Link to comment Share on other sites More sharing options...
ZachSaucier Posted July 23, 2020 Share Posted July 23, 2020 You just need to create media queries that match the surrounding parts. Something like this: See the Pen rNxROLG?editors=0010 by GreenSock (@GreenSock) on CodePen 1 Link to comment Share on other sites More sharing options...
ZachSaucier Posted July 23, 2020 Share Posted July 23, 2020 The reason for this is that there is no "else" functionality with media queries. Alternatively you could try setting things up with if/else using JS's .matchMedia but then you'll be responsible for killing off and recreating ScrollTriggers yourself, which is obviously not optimal. Link to comment Share on other sites More sharing options...
emjay Posted July 24, 2020 Share Posted July 24, 2020 15 hours ago, ZachSaucier said: You just need to create media queries that match the surrounding parts. Something like this: Hello @ZachSaucier, I already tried this, but with this solution we've many situations we're multiple breakpoints are matching (always if the aspect ratio = 4/3 or 2/1). 1032 * 774 = 4/3 = 1.33333 -> so 3 and 4 from your example will match both at this resolution, the next would be 1037 * 777, and so on... 1032 * 516 = 2/1 = 2 -> so 2 and 4 from your example will match, the next would be 1034 * 517, 1036 * 518, ... I updated the codepen with some output: See the Pen 0cb7376d2d2ff34bff65a1b1bd633faa?editors=0001 by emjay (@emjay) on CodePen Hope you have another idea. Thanks, Martin Link to comment Share on other sites More sharing options...
ZachSaucier Posted July 24, 2020 Share Posted July 24, 2020 6 hours ago, emjay said: Hope you have another idea. Like I said, the alternative is to use JS's built in method of matchMedia and handle the killing and reconstructing yourself. Link to comment Share on other sites More sharing options...
emjay Posted July 27, 2020 Share Posted July 27, 2020 On 7/14/2020 at 2:11 AM, GreenSock said: Furthermore, you can optionally return a function that'll be called when the breakpoint is no longer active (teardown). I figured you might appreciate that. @ZachSaucier as you've said, this "is obviously not optimal". Would this teardown function, which was mentioned by @GreenSock help in my case? That was the reason for me to ask in this thread. Thanks, Martin Link to comment Share on other sites More sharing options...
ZachSaucier Posted July 27, 2020 Share Posted July 27, 2020 6 hours ago, emjay said: Would this teardown function, which was mentioned by @GreenSock help in my case? That was the reason for me to ask in this thread. Sorry, what teardown function are you talking about? Link to comment Share on other sites More sharing options...
emjay Posted July 27, 2020 Share Posted July 27, 2020 On 7/14/2020 at 2:11 AM, GreenSock said: Thanks for sharing your solution! I rolled up my sleeves and worked on accommodating a use case like this, so you can add the same breakpoint multiple times and it should work in 3.4.1. Here's a preview: https://assets.codepen.io/16327/ScrollTrigger.min.js Furthermore, you can optionally return a function that'll be called when the breakpoint is no longer active (teardown). I figured you might appreciate that. Better? @ZachSaucier I mean this post, #6 in this thread. Link to comment Share on other sites More sharing options...
GreenSock Posted July 27, 2020 Share Posted July 27, 2020 @emjay What exactly do you want to happen? If there are multiple matching media queries and you only want ONE to apply at any given time, what logic do you want to use to decide which one? What do you want ScrollTrigger to do to help you that it's not currently doing? Link to comment Share on other sites More sharing options...
GreenSock Posted July 28, 2020 Share Posted July 28, 2020 I re-read things and I think I understand what you're asking for - a "none" option (not-matching). Here's a helper function I whipped up that should deliver that kind of thing - you'd use it just like ScrollTrigger.matchMedia(): function matchMediaOrNot(vars) { let queries = [], copy = {}, isMatching, notMatchingFunc = vars.none, check = () => { let i = queries.length; while (i--) { if (queries[i].matches) { isMatching || ScrollTrigger.revert(false, "none"); isMatching = true; return; } } isMatching = false; return notMatchingFunc(); }, wrap = func => () => { check(); let result = func(); return () => check() || result; }, p; for (p in vars) { (p !== "none") && queries.push(window.matchMedia(p)); copy[p] = wrap(vars[p]); } ScrollTrigger.matchMedia(copy); check(); } Usage: matchMediaOrNot({ "(max-width: 1024px)": function() { console.log("1") }, "(min-width: 1025px) and (min-aspect-ratio: 4/3) and (max-aspect-ratio: 2/1)": function() { console.log("2") }, "(min-width: 1025px) and (max-aspect-ratio: 4/3)": function() { console.log("3") }, "none": function() { console.log("not matching!"); }, }); Does that deliver what you wanted? 2 Link to comment Share on other sites More sharing options...
emjay Posted July 28, 2020 Share Posted July 28, 2020 Hello Jack @GreenSock, thanks for re-reading and posting a solution. I updated my demo pen and tried the solution. Like all your stuff, it works great, thanks so much for helping with this problem. See the Pen 0cb7376d2d2ff34bff65a1b1bd633faa?editors=0011 by emjay (@emjay) on CodePen Thanks again, Martin 1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now