Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

using library without vue-router #15

Open
719media opened this issue Jun 12, 2020 · 18 comments
Open

using library without vue-router #15

719media opened this issue Jun 12, 2020 · 18 comments
Assignees
Labels
enhancement New feature or request

Comments

@719media
Copy link
Contributor

719media commented Jun 12, 2020

I was thinking of ways to use the library for transforms that don't involve vue-router. For example, a modal opening that doesn't change the history/url.
For example, imagine the code

<template>
  <div>
    <div style="display: flex;">
      <div style="color: blue;" v-shared-element:test="{ includeChildren: true}">Blue</div>
      <div style="margin-left: auto; color: red;" v-shared-element:test="{ includeChildren: true}">Red</div>
    </div>
    <button type="button" @click="condition = !condition">Change</button>
  </div>
</template>

And having that work v-shared-element

@719media
Copy link
Contributor Author

I know you can use the $.createIllusoryElement extended methods from the plugin to spell it out, I just figured to make it more automatic using only the directive

@719media
Copy link
Contributor Author

I got it working ugly like here:
https://codesandbox.io/s/v-shared-element-test-920o7
by just exposing sharedElementCache and sharedElementCandidates on the export and manipulating them directly in Home.vue

@719media
Copy link
Contributor Author

Anyway, everything inside doChange method in the example above is just library code, so if that method was exportable from the library, you could then run that method explicitly inside of whatever code before the change, and get the desired result.

Two caveats:
I did not test with keep-alive
I wasn't able to get it to work unless I used vue key="blue" key="red" so that vue wouldn't just re-use the DOM (I needed to get the v-shared-element directive to fire the "inserted" hook again). This is not so "beginner-friendly"... but the only way around it I feel is to lose the ease of having "new inserted elements" just automatically work as long as you have run the method like in the example.
In other words, the ideal solution thus far is something like:

import {
  someNewlyExposedMethodThatCapturesElementsPotentiallyAboutToTransition,
} from "v-shared-element";

export default {
  ...,
  methods: {
    ...,
    someThingICareAbout() {
      someNewlyExposedMethod();
      this.changeToTemplateVIf = true;
    }
  },
}

@719media
Copy link
Contributor Author

719media commented Jun 12, 2020

Actually, I'm totally overlooking a probably easier solution: using unbind in the directive to capture the stuff moving out, I'll mess around with that for a minute...

@719media
Copy link
Contributor Author

719media commented Jun 12, 2020

So, unbinding COULD work, but it is a little more work than I have time for right now. You may know better being the author on how it could possibly work... from what I can tell, unbind is always fired, even when the entire component is destroyed (say on a router-view change), so parts of what is happening in the routeGuard COULD be ported over to an unbind potentially, to try and simplify this.

The problem I was having was the shift in the mindset of the router guard having a singular event (a route change), to no more singular event (at least, not one as "universal" as a router change, but just a user-defined one in a method... and we're trying to get away from having to explicitly do things), and so doing things like "clear the cache before I start collecting eligible v-shared-elements" was a little more difficult without being explicit...

So since we have a bunch of elements unbinding, keeping track of them at that micro level... anyway hopefully you see where I'm going with this... but basically you are keeping track of cache using inserted and unbind, instead of inserted and route guard

@justintaddei
Copy link
Owner

Looks like you've narrowed the problem down. I really like the idea so long as we can pull it off without complicating the current behavior (should be possible from what I can tell). It's hard to really look through the code from my phone but think I've got the gist of what you've done. I'm on the road home right now and I'll play around with it as soon as I'm back. I think there's a lot of cool potential in this!

@justintaddei justintaddei self-assigned this Jun 13, 2020
@justintaddei justintaddei added the enhancement New feature or request label Jun 13, 2020
@justintaddei justintaddei modified the milestone: v2.2.0 Jun 13, 2020
@719media
Copy link
Contributor Author

719media commented Jun 18, 2020

Here's an example that works with routing as well (did not test keep-alive, however). Just exposed a method snapshotCache which is understood to be a method run just prior to "removing" anything that you want to transition. This is used in the router:

router.beforeEach((to, from, next) => {
  snapshotCache();
  next();
});

as well as a method:

doChange() {
  snapshotCache();
  // now execute
  this.condition = !this.condition;
}

Instead of clearing the candidates on snapshot (the router guard in the current version), I clear it on the unbind, since in theory multiple transitions can be happening. That is to say, a candidate could be transitioning while another candidate remains active (unlike on router-view, where EVERYTHING unbinds and new candidates are loaded)

https://codesandbox.io/s/v-shared-element-test3-hp7jt?

@719media
Copy link
Contributor Author

719media commented Jun 18, 2020

So, the problem with trying to keep sharedElementCache on every unbind is that you don't know in advance of the element dropping (automatically) if you are GOING to be using it. So your cache would grow over time as certain elements would not be used, and you wouldn't know. So, you could make a rule to clear an element after x time if it wasn't used, but the complications of that are undesirable and wonky.

After using, I like how the snapshotCache method works my purposes. You merely call it every time you are (about to) do a transition and want a copy of the things that you may want to have use a shared transition. If all you are using is this library in conjunction with router-view, then the change is behind the scenes (there is no change to how you use this library).

It works with keep-alive method:
https://codesandbox.io/s/v-shared-element-test4-dx2uw

The basic change from current 2.1.0 version is:
*moving the routeGuard stuff into an exportable method, and simplify the routeGuard to just call this method
*removal of sharedElementCandidates.clear() from this method, and instead have that happen in unbind

I think that's it! I tested it a bit with my stuff, and it works great!

@719media
Copy link
Contributor Author

719media commented Jun 18, 2020

One minor thing I noticed was what happens when you navigate to another route where the transition actually isn't "moving" anywhere (e.g. from /alt to /alive). I don't think that illusory should try and figure this out, as determining if a transition should happen should not be based on calculation of all of the styles to see if anything has changed (IMO). Sounds problematic anyway?

Another alternative would be some disabling attribute. Since disabling is dependent on "from" as well as "to" in the case of router (e.g. when going from /a to /b, I want transition, but from /a to /c I do not), and could be based on other elements, perhaps there is a use for a "disabled" attribute like so:

v-shared-element:test="{
disabled
}"

The presence of a disabled attribute would disqualify the shared element from the snapshot perhaps? Easier than removing the v-shared-element attribute completely? Basically just a check for if (candidate.options.disable !== true) { on the creation of sharedElementCache

Anyway, just a thought, don't mean to overkill this thread. Just bringing it up since I noticed that behavior and was thinking of most efficient way to prevent it. If this is something you think is useful, then let's create a new issue for it.

@719media
Copy link
Contributor Author

Update: there is a possible race condition that causes an issue. The issue is that, in some circumstances, the "new" element will be inserted before the "old" element is unbind, and in some circumstances, the reverse. The problem is that when the new element is inserted first, then the old element unbind fires after, and removes the new element from the map.

@719media
Copy link
Contributor Author

719media commented Jun 18, 2020

I'm currently testing having

sharedElementCandidates.forEach((candidate, id) => {
    if (!document.body.contains(candidate.element)) {
      sharedElementCandidates.delete(id);
    }
  });

at the top of snapshotCache() method in the last above-mentioned codesandbox example. Also removed the unbind addition completely. Seems to work so far, even with keep-alive.

@justintaddei
Copy link
Owner

That looks really good so far. You also seem pretty close done. I'll be able to help experiment more tomorrow.

On a side note, I recently discovered the framer-motion library. I haven't personally used it yet but from the examples it seems really cool. There currently isn't an equivalent for Vue, to my knowledge. Would you be interesting in helping me port it to Vue? (Definitely still keeping this project alive though, so don't worry)

@719media
Copy link
Contributor Author

The work done in this issue so far is definitely just brainstorming, I wouldn't say any concept so far is something I think is ready for a PR.

Maybe, I'll take a look at framer-motion.

@719media 719media reopened this Jun 20, 2020
@justintaddei
Copy link
Owner

After playing with this for a long time and still not getting anything that I'd consider reliable enough to ship, and dealing with some performance issues when many shared-elements are present on a single route, I think is a feature best kept for a separate package. If I find some free time I may work on another directive (or component) that is capable of performing such a task.

In the meantime, I'm going to close this issue.
I will reply to this thread if I make any more progress on this. Thank you for all your input and time spent brainstorming!

@719media
Copy link
Contributor Author

@justintaddei you still around? this feature look any more doable now with the latest vue 3 changes?

@justintaddei
Copy link
Owner

justintaddei commented Jan 10, 2023

@719media hey! I'm still around. I've been really busy with work and personal stuff so I haven't had much time to keep up with my projects recently. This is probably doable with a bit of a rewrite.

I'm absolutely interested in including this if it's feasible. I plan on looking into this myself at some point. If you would like to work on it, I'd be more than willing to review any MRs.

I should have more free time in a month or 2.

I'll reopen this issue so it stays on my mind :)

@justintaddei justintaddei reopened this Jan 10, 2023
@mrleblanc101
Copy link

I look for a library to do exactly that, a shared element transition with a modale and can't find one 😭

@camhammel
Copy link

Any update on this? 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants