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

Usage of x-transition with responsive elements #235

Closed
georgeboot opened this issue Mar 5, 2020 · 35 comments
Closed

Usage of x-transition with responsive elements #235

georgeboot opened this issue Mar 5, 2020 · 35 comments

Comments

@georgeboot
Copy link
Contributor

x-transition only works with x-show and x-if.

However, in many cases, these tools can't be used. For example, with an element that you only want to show on small screens.

In these cases, you wil probably use:

<div :class="{'block': open, 'hidden': !open}" class="hidden sm:hidden">

How could one still get transition support to these cases?

@iksaku
Copy link

iksaku commented Mar 5, 2020

I understand that you have an element that will only show on small screens, and assuming you’re using Tailwindcss, we could hack something like this:

<div
    x-show=“open”
    class=“sm:hidden”
>
    ...
</div>

Now, let me explain:

  • x-show=“open” as we know, will hide the element until the reactive variable is toggled. What it does is not to interfere with classes, but it does directly work with the element’s style property, appending style=“display: none;” to the element, regardless of which classes you’re using nor the screen size.
  • class=“sm:hidden” will keep the element hidden for bigger screens.

With this, you should be able to keep adding transitions and animations on the small screen, while keeping everything hidden for the bigger ones.

@georgeboot
Copy link
Contributor Author

Yes this could work, provided that the class styles are more important than the elements inline styles that Alpine applies.

This is presently (not always) the case, right?

@iksaku
Copy link

iksaku commented Mar 5, 2020

Class is prioritized over Inline-styling in all browsers, so this should work to keep the “toggle” only work for the small screen

@georgeboot
Copy link
Contributor Author

Ok well in that case, this solves my issue. Thanks a million!

@iksaku
Copy link

iksaku commented Mar 5, 2020

Glad to help!

Try it out and ping if this solves your issues or if it doesn’t 😄

@HugoDF
Copy link
Contributor

HugoDF commented Mar 5, 2020

Class is prioritized over Inline-styling in all browsers, so this should work to keep the “toggle” only work for the small screen

Are you sure about this? Pretty sure you can override class contents with an inline style.

@iksaku
Copy link

iksaku commented Mar 5, 2020

@HugoDF, you’re right, my bad, inline style property has higher priority. Thanks for the heads up!

Anyway, for this specific case it will not overlap the logic given before.

  • class=“sm:hidden” will keep the element hidden from bigger screen sizes.
  • x-show=“open” will toggle the presence of the inline style=“display: none;”, so while it’s shown, it will hide the element completely, and when toggled, the property will be removed, falling back again to the class specification.

@HugoDF
Copy link
Contributor

HugoDF commented Mar 5, 2020

😉 I guess your fix didn't rely on that precedence rule 👍

@georgeboot georgeboot reopened this Mar 6, 2020
@georgeboot
Copy link
Contributor Author

I've re-opened this issue since it is something that might need a better workflow.

It indeed works in this particular case because the styles are defined mobile-first and we anyway hide the menu on larger screens.

If I wanted to do the opposite (hide menu on small screens, toggle on larger) it would not work.

@iksaku
Copy link

iksaku commented Mar 7, 2020

I think this is becoming more of a CSS thing than an Alpinejs one... But let’s give it a shot...

For the inverse, hide on small screen and show on bigger ones, do something like this:

<div
    x-show=“open”
    class=“hidden sm:block”
>
    ...
</div>

Note: The class sm:block can be actually changed for any other class that sets display values other than none.

@FYITom
Copy link

FYITom commented Apr 3, 2020

Inline styles will always override CSS classes unless the CSS class has !important applied, so if you toggled something to recieve display: block it will not hide on mobile.

The way around this would be either create classes with !important or ignore x-show transitions and use TailwindCSS to drive your animation. The latter is what I go for as I've found x-show transitions to be quite buggy. #170

@iksaku
Copy link

iksaku commented Apr 3, 2020

@FYITom I don’t think I’m understanding your whole point, but as far as I can understand, you’re defining display classes during transition steps, which I think you should avoid. Instead, define display classes in the object class directly, and keep animations or transforms at transition steps.

The following is present in one of my projects. It properly switches x-show and only shows in XS and SM screens, and neither the object itself nor animations show in MD+ screens:

<div
    x-show=“open”
    x-transition:enter=“transform duration-200”
    x-transition:enter-start=“opacity-0 scale-90”
    x-transition:enter-end=“opacity-100 scale-100”
    x-transition:leave=“transform duration-200”
    x-transition:leave-start=“opacity-100 scale-100”
    x-transition:leave-end=“opacity-0 scale-90”
    class=“block md:hidden p-4 border rounded shadow”
>
    Hello there!
</div>

@SimoTod
Copy link
Collaborator

SimoTod commented May 23, 2020

@georgeboot Are you happy to close this issue?

The responses above are correct and it's more about how css work then Alpine.

To recap

  • inline styles have precedence on classes (unless a rule have is defined as !important)
  • the show directives uses an inline "display: none" to hide elements
  • hiding elements on specific breakpoints will work as expected.

Thanks

@georgeboot
Copy link
Contributor Author

Hmm I’m not fully sure. @FYITom points out some issues.

I guess there are ways to work around this, but it isn’t ideal. Many people use Alpine together with Tailwind CSS, and this is a widely spread use case.

For example: Tailwind UI (commercial product from guys of Tailwind CSS) has places where this is the exact thing. This means that no transitions are using on mobile or desktop only elements, simple because it’s a lot of hacking to get it to work.

On the other hand don’t I like to keep an issue open, while I’m myself not actively trying about ways to fix it. So there’s that.

I think if we could come up with something like x-show.class="visible classes" and x-show.hidden.class="hidden classes", this would allow people to customise the way Alpine hides or shows elements with x-if. This would then also fix the above issue.

Worth exploring?

@HugoDF
Copy link
Contributor

HugoDF commented May 23, 2020

@georgeboot isn't that already possible with either the x-transition API or using :class="display ? 'visible classes' : 'hidden classes'"?

@iksaku
Copy link

iksaku commented May 23, 2020

So, what you’re looking for is for screen resize class transitions @georgeboot? Or merely class switching based on screen sizing?

@georgeboot
Copy link
Contributor Author

@HugoDF yes you can toggle classes using :class. But if you want to use x-transition, you need to use x-if or x-show.

@georgeboot
Copy link
Contributor Author

georgeboot commented May 23, 2020

I think not everyone is getting my point here. Let me summarise.

If you want to use x-transition, you need to use either x-show or x-if. This effectively adds an inline style to the element: display: none.

Inline styles take precedence over classes.

Because if this, some responsive scenarios are not possible. For example: you want to toggle and animate an element on mobile screens, but have it always visible on larger screens.

If you use :class="isVisible ? 'block' : 'hidden lg:visible'" you could toggle the element on small screens (and it will always remain visible on larger screens) but you can’t use x-transition. Because we are not using x-if or x-show.

If we use x-show, it would also hide in larger screens, even if we set lg:block

@SimoTod
Copy link
Collaborator

SimoTod commented May 23, 2020

@georgeboot Can you post a codepen or an example where we can replicate the issue so we can have a better understanding of the problem? Thanks

@SimoTod
Copy link
Collaborator

SimoTod commented May 23, 2020

Sorry, I posted while you were sending your second message. So you are setting a generic hidden and overriding large screens, right? Don't you get the same result if you hide small and medium screens?

@SimoTod
Copy link
Collaborator

SimoTod commented May 23, 2020

@georgeboot To clarify, I mean something like:
https://codepen.io/SimoTod/pen/GRpeOrQ

UPDATE: It actually works with the generic class as well: https://codepen.io/SimoTod/pen/OJyqOjo

Sorry, I think I need to see an example where it's not working to understand the issue because it looks like it should work to me.

@nlehman06
Copy link

How about this for an example where it is not working. I'd like for the options to ALWAYS be displayed on large screens. However, on smaller screens, I'd like for it to be displayed only if it is toggled.

https://codepen.io/nlehman06/pen/LYGoENq

I know I can do it with :class toggling, but that will make it so I can't do transitions.

@SimoTod
Copy link
Collaborator

SimoTod commented Jul 27, 2020

What do you suggest @nlehman06? There are a few ways to do it. For example you could have a property set to true if the screen is large and update the property on @resize.window.debounce.
The show condition will be x-show="open || isLarge"
I mean, Alpine is flexible and you can write any javascript statement for your conditions so it's justuo to you.
Or you could add a css animation and toggle it using x-class.

@nlehman06
Copy link

Sorry, I don't really have much of a suggestion. I just ran into this issue myself and found this discussion and the last thing said was "I need to see an example" so I thought I'd post my current situation.

Thanks for the work-arounds. I'll give the @resize a shot.

@SimoTod
Copy link
Collaborator

SimoTod commented Jul 27, 2020

No worries, I asked in case you had something in mind. Alpine Transitions can be applied only on x-if and x-show. Applying them to x-bind:class would be quite challenging and would have poor performances: the code would have to understand if a class changed the status of the display property and call the transition in case (which means it should apply the class, check display, remove the class, start the transition and apply the class again, hoping the DOM won't flicker in the meantime).
Since you use tailwind, you can actually use the plugin function to generate your own utility classes with '!important' to force display:block on large screens (https://tailwindcss.com/docs/plugins/).

@bep
Copy link
Contributor

bep commented Jul 31, 2020

Since you use tailwind, you can actually use the plugin function to generate your own utility classes with '!important' to force display:block

The above is (often) a little heavy just to solve this particular problem.

I had a head-scratching moment (or to be honest: 2 head-scratching hours) with this particular problem myself. What I found is working nicely is something like this:

@screen md {
  .block-important {
    display: block !important;
  }
}
<div class="block-important" x-show.transition="open">
</div>

@ryangjchandler
Copy link
Contributor

I think this issue can be closed now.

As @SimoTod mentioned, supporting x-transition for class bindings would add a whole level of complexity to Alpine and potentially damage the stability of the library.

@bep has left a good tip above, but there is also the tip from @SimoTod about adding !important to your rules.

@georgeboot
Copy link
Contributor Author

@ryangjchandler yeah agreed. Thanks for the solutions provided!

@ryangjchandler
Copy link
Contributor

@georgeboot - no worries, that's what we're here for!

@danielreales7
Copy link

danielreales7 commented Oct 4, 2020

Hi, I have the same problem.

I'll show the code:

<div class="flex items-center justify-between px-4 py-3 sm:p-0 shadow-sm">
    <div>
        <a href="{{ route('home') }}" class="">
            {{ config('app.name') }}
        </a>
    </div>
    <div class="sm:hidden">
        <button @click="open = ! open" type="button" class="block text-gray-500 hover:text-gray-900 focus:text-gray-900 focus:outline-none">
            <!-- Heroicon name: menu -->
            <svg class="h-6 w-6 fill-current" viewBox="0 0 24 24">
                <path x-show="open" fill-rule="evenodd" d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z"/>
                <path x-show="!open" fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"/>
            </svg>
        </button>
    </div>
</div>
<div x-show="open" :class="open ? 'block' : 'hidden'"
     x-transition:enter="transition ease-out duration-300"
     x-transition:enter-start="transform opacity-0 scale-95"
     x-transition:enter-end="transform opacity-100 scale-100"
     x-transition:leave="transition ease-in duration-75"
     x-transition:leave-start="transform opacity-100 scale-100"
     x-transition:leave-end="transform opacity-0 scale-95"
     class="px-2 pt-2 pb-4 sm:flex sm:p-0"
     @click="open = false">
    <a href="{{ route('home') }}" class="{{ setActive('home') }} block px-2 py-1 font-semibold rounded hover:bg-gray-50">
        <span>@lang('Home')</span>
    </a>
    <a href="{{ route('about') }}" class="{{ setActive('about') }} mt-1 block px-2 py-1 font-semibold rounded hover:bg-gray-50 sm:mt-0 sm:ml-2">
        <span class="">@lang('About')</span>
    </a>
    <a href="{{ route('projects.index') }}" class="{{ setActive('Projects') }} mt-1 block px-2 py-1 font-semibold rounded hover:bg-gray-50 sm:mt-0 sm:ml-2">
        <span>@lang('Projects')</span>
    </a>
    <a href="{{ route('contact') }}" class="{{ setActive('Contact') }} mt-1 block px-2 py-1 font-semibold rounded hover:bg-gray-50 sm:mt-0 sm:ml-2">
        <span>@lang('Contact')</span>
    </a>
</div>

If I delete x-show="open" the menu show in md screens and the hamburger menu in mobile. But If I want to use transitions and I add the line x-show="open" the menu in md screens dissapear and in the mobile works perfectly.

What is the problem?

Thank you so much!

@SimoTod
Copy link
Collaborator

SimoTod commented Oct 4, 2020

open resolves to false which means that the menu it's hidden by default for all breakpoints. You don't need :class, x-show will always overrule it and it just adds confusion.
It works for mobile because you have a click event setting open to true.
Your x-show should be something like window.innerWidth >= 768 ¦¦ open (literally show when viewport is at least 768ph, md, or for smaller screens when open is set to true).

@danielreales7
Copy link

@SimoTod wow! Thank you so much!!

I didn't know that I could use window.innerWidth inside alpine. I've been trying to fix this for two days and I couldn't. I am really new to Alpine and Tailwind CSS.

Thanks again!

@SimoTod
Copy link
Collaborator

SimoTod commented Oct 4, 2020

No worries. FYI, You can use any valid js statements inside Alpine directives. 👍

@zenbug
Copy link

zenbug commented May 28, 2021

In case someone else comes across this, I made a Pen using @bep's solution:
https://codepen.io/mikemella/pen/vYxeeXN

@Oleafeon
Copy link

In tailwind 3 you can use sm:!block to add important to the styling.
Thats because of Tailwind’s new Just-In-Time mode

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

No branches or pull requests