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

Consider allowing the user to pass a string media query rather than just a max width value #35

Open
stephantabor opened this issue May 20, 2019 · 22 comments

Comments

@stephantabor
Copy link

would solve issues like #18, #34

What i'm looking to do personally is just use min-width media queries so i can easily match them up with our predefined in our css

@VinnyFonseca
Copy link

Same here. It states it's a mobile first approach but uses max-width for breakpoint recognition. An option for min-width would be great.

@flyingL123
Copy link

Yes, please add this. I just spent a while trying to figure out why things weren't working correctly. I was just trying to set the breakpoints to be the same as my tailwind configuration:

Vue.use(VueMq, {
    breakpoints: {
        sm: 640,
        md: 768,
        lg: 1024,
        xl: 1280,
    }
});

It took me longer than I would like to admit to realize that this library was using max width while tailwind and most other CSS frameworks use min width. It will be easier to take the configuration directly from tailwind and use it within vue if they are all min width.

@stephantabor
Copy link
Author

What I ended up doing is just using window.matchMedia and setting some state inside of Vuex (or just a Vue component instance when I’m not using vuex) for whatever breakpoints match the framework I’m using, or some other special cases. It’s not as nice as using this lib would be, but I don’t need the breakpoints too often in templates / js so it’ll do for now.

@flyingL123
Copy link

I just adjusted the settings to be max-width but still correspond to the css min-width breakpoints

Vue.use(VueMq, {
    // These are max-width settings
    breakpoints: {
        sm: 767,
        md: 1023,
        lg: 1279,
        xl: Infinity,
    }
});

Not as convenient, but it seems to work fine, unless I'm missing something?

@AndrewBogdanovTSS
Copy link

AndrewBogdanovTSS commented Jan 27, 2020

I would really like to switch to vue-mq, but this specific issue is keeping me from doing so. As of now approach that I'm using is pretty similar to what @stephantabor has described. I created a store module and a mixin called media:

media store

import breakpoints from '@/config/tailwind/breakpoints'

export const state = () => ({
  windowWidth: 0
})

export const getters = {
  is: state => breakpoint => {
    return state.windowWidth >= breakpoints[breakpoint]
  }
}

export const mutations = {
  setWindowWidth(state, payload) {
    if (payload !== state.windowWidth) {
      state.windowWidth = payload
    }
  }
}

media mixin

export default {
  methods: {
    media(brakepoint) {
      return this.$store.getters['media/is'](brakepoint)
    }
  }
}

that way I can get exactly the behavior I want. The only caveat to this solution is that I have to subscribe to resize event when app starts:

window.addEventListener('resize', debounce(this.setWindowWidth, 300))

@nathanchase
Copy link

Yes, please! I use min-width media queries and this doesn't support that:

I have the following breakpoints:

@custom-media --larger-than-skyscraper (min-width: 160px);
@custom-media --larger-than-iphone-se (min-width: 374px);
@custom-media --larger-than-mobile (min-width: 414px);
@custom-media --larger-than-phablet (min-width: 550px);
@custom-media --larger-than-leaderboard (min-width: 728px);
@custom-media --larger-than-tablet (min-width: 750px);
@custom-media --larger-than-desktop (min-width: 1000px);
@custom-media --larger-than-ipad (min-width: 1024px);
@custom-media --larger-than-desktop-hd (min-width: 1200px);
@custom-media --full-size (min-width: 1440px);

but there's no way to match those with vue-mq.

@flyingL123
Copy link

flyingL123 commented May 28, 2020

@nathanchase can't you just adjust the mq settings to use max-width values, like I mentioned above?

#35 (comment)

@stephantabor
Copy link
Author

@flyingL123 it can work for some simple cases, but the main reason that I made this issue was because using matchMedia and allowing any media queries has a few other advantages:

  • vue media queries can match your css ones exactly
  • a bunch of other useful stuff media queries can do which could then be done with this lib
    • print
    • landscape v portrait
    • non-px units

These are just some things i've personally done with my home rolled solution, i'm sure there are a bunch of others I haven't considered

@nathanchase
Copy link

@flyingL123 No, because my design is built around the breakpoints being "larger than" a certain size, not "less than" a certain size.

@stephantabor
Copy link
Author

stephantabor commented May 28, 2020

@nathanchase I think the person meant you could convert your min-width to max-width, e.g.

@custom-media --larger-than-desktop-hd (min-width: 1200px);
@custom-media --full-size (min-width: 1440px);

becomes

ue.use(VueMq, {
    // These are max-width settings
    breakpoints: {
        largerHd: 1339,
        fullSize: Infinity,
    }
});

conversion might be off, just an example

But it's not perfect and annoying that you have to then mentally convert your css mq to the js one, being able to have the same exact media queries would be waaaaaay better

@nathanchase
Copy link

That doesn't work because I need the change (show/hide of a component) to happen exactly at the minimum width, not at the last pixel before the next breakpoint.

@nathanchase
Copy link

Decided to switch libraries to the much more full-featured https://github.com/reegodev/vue-screen instead. Cheers.

@flyingL123
Copy link

@nathanchase good find, thanks!

@AndrewBogdanovTSS
Copy link

@nathanchase wow, that lib looks really awesome! Thanks for the link!

@nathanchase
Copy link

Well, vue-screen also causes SSR/client hydration errors (Using Nuxt), so now I don't know what to do. Stuck trying to come up with an implementation that works, but nothing exists.

@flyingL123
Copy link

@nathanchase I am still confused why you can't adjust your values so that mq will work. For example, my css breakpoints are:

breakpoints: {
  sm: 640,
  md: 768,
  lg: 1024,
  xl: 1280,
}

Using that as an example, md is from 768-1023, and lg is from 1024-1279.

Then, for mq, they are defined like this:

breakpoints: {
  sm: 767,
  md: 1023,
  lg: 1279,
  xl: Infinity,
}

Going through the same example, now these are max-width settings, which means mq treats 768-1023 as medium, and 1024-1279 as large. The ranges are exactly the same, just like you want, aren't they?

@nathanchase
Copy link

Either way, I can't actually use them in a computed property the way I need to.

I need to be able to do v-if='biggerThanMobile' on an element - which is unsupported. To do that, I'd have to check against EVERY breakpoint in a computed property, like:

computed() {
    return biggerThanMobile = this.$mq === 'largerThanMobile' ||  this.$mq === 'largerThanPhablet' || this.$mq === 'largerThanLeaderboard' || this.$mq === 'largerThanTablet' || this.$mq === 'largerThanDesktop' || this.$mq === 'largerThaniPad' || this.$mq === 'largerThanDesktopHD' || this.$mq === 'fullSize';
}

Instead of something that figures out anything bigger than the named breakpoint.

Not ideal.

@flyingL123
Copy link

Not ideal sure but it’s a one time inconvenience to write a small object that includes a method to check those ugly conditionals for each breakpoint and return true or false. Then use it in whatever components need it. Either through the provide inject api or directly as a separate import.

@AndrewBogdanovTSS
Copy link

AndrewBogdanovTSS commented May 30, 2020

@nathanchase you will get hydration errors anyway, there's no cure from that since it's how Nuxt works, it's not related to one lib or another. The actual problem is that in SSR phase you don't have a window object thus no media queries so Nuxt will always render "mobile" version of your app first, but once client phase kicks in - the lib changes your DOM structure and you get a hydration error. The rules here are simple - try to use vue-screen in v-if as less as possible. The better way is to apply some specific class that will alter the look. I think it will also work good if you will provide the same structural element in v-else, you can just put a display:none on it with a class, but the goal here is to always try to keep identical DOM structure on all breakpoints.

@nathanchase
Copy link

nathanchase commented May 30, 2020

I think it will also work good if you will provide the same structural element in v-else, you can just put a display:none on it with a class, but the goal here is to always try to keep identical DOM structure on all breakpoints.

That's the problem though, is that I want to be able to lazy load Vue components as needed. There are components specific to a mobile view that don't need to be loaded for a desktop user, and vice-versa. Yes, I can (and have) been using CSS to show/hide content, but it still loads both sets of content and just hides some of it - wasting time downloading content that will likely not be shown UNLESS the user changes their viewport.

In addition, I do need SSR to fetch and render out information for SEO purposes, and by default, Googlebot uses a mobile user agent as its primary crawler. This is problematic, as the content will not be the same (and shouldn't be) for mobile as it would be on desktop - so I do still need what amounts to two customized versions of the site depending on a "Smartphone" crawler or a "Desktop" crawler.

My hope is that there's some way to be able to use v-if on elements accordingly. I could use user agent sniffing, but that seems a lot less resilient than viewport widths.

It's a difficult problem to solve!

@AndrewBogdanovTSS
Copy link

@nathanchase I understand your pain but can't give you a straight solution to the problem. If you will find something that can mitigate this issue - let me know, please. I'm also curious about how can it be fixed in the right way :)

@SergeyDarnopykh
Copy link

SergeyDarnopykh commented Dec 17, 2020

@nathanchase @AndrewBogdanovTSS Hey guys, I think I've solved a problem with vue-mq and SSR. Think you might be interested.

So I was searching for a solution, and as you found none. So I've spent the whole day on this and finally I've got a working solution (it seems so).

My idea: we render component with ssr, but don't hydrate it on the client immediately. We check if the breakpoint on the client matches the defaultBreakpoint / breakpoint on a server (I use device-detector-js on a server to determine user's device from user agent), and if the two breakpoints match - we do hydration, if they don't - we render the component from scratch. I created a wrapper component for this purpose, to wrap mq-related code with it. Here's the code, though it uses some vue-property-decorator and some additional utils (let me know if you want to see them)

<template>
    <div>
        <LazyHydrate never :trigger-hydrate="doHydration" v-if="doHydration">
            <slot></slot>
        </LazyHydrate>

        <NoSsr v-else>
            <slot></slot>
        </NoSsr>
    </div>
</template>

<script lang="ts">
    import { Vue, Component } from 'vue-property-decorator';
    import LazyHydrate from 'vue-lazy-hydration';
    import NoSsr from 'vue-no-ssr';
    import { isServer } from '@vue-storefront/core/helpers';
    import detectBreakpoint from 'theme/utils/detect-breakpoint';

    @Component({
        components: {
            LazyHydrate,
            NoSsr
        }
    })
    export default class ResponsiveHydrate extends Vue {
        public name: string = 'ResponsiveHydrate';

        public doHydration: boolean = isServer;

        mounted() {
            this.doHydration = this.$mq === detectBreakpoint();
        }
    }
</script>

Ps: Device detector would've been enough by itself for solving hydration issue (cause it's good at detecting mobile / tablet / desktop), but we can't 100% guarantee that the user of tablet will have the screen resolution we will set it to (768-1024 by default) or that the user on desktop won't use dev tools to make screen smaller. Thus this solution above.

Let me know if this was any help to you!

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

6 participants