Over the years, media queries have helped web developers immensely with responsive design. It gives us a way to define CSS rules for a particular breakpoint relative to the browser’s viewport. As the web evolves, web components are becoming more popular and component developers are increasingly interested in applying responsive styles based on the dimensions of the container rather than the viewport. We can achieve this by leveraging the built in ResizeObserver (or fallback to the resize observer polyfill if Edge, IE and Safari support is needed).
How do we do this?
First, install the resize observer polyfill through npm if needed. Then we can create a mixin which imports the Resize Observer and sets the state of each component size property, for each component. Resize Observer will allow us to easily observe changes to the component’s container size.
In our mixin, we create the ResizeObserver instance in our constructor and observe changes to the component size in the connectedCallback.
const internalComponentSize = base => class extends base { static get properties() {...} constructor() { super(); // set default pixel values for each component size this._pixelValues = { componentXsm: 320, componentSm: 600, componentMd: 960, componentLg: 1280, componentXlg: 1600, }; // Create global ResizeObserver if it doesn't exist if (!window.myResizeObserver) { // resize observer will run the function _componentResized // every time resize is triggered from components window.myResizeObserver = new ResizeObserver(this._componentResized); } } connectedCallback() { super.connectedCallback(); // observing all 'this' on resize of the component window.acc.ro.observe(this); } disconnectedCallback() { super.disconnectedCallback(); // unobserving all 'this' on chnage of state of the component window.acc.ro.unobserve(this); } ... }; export const ComponentSize = internalComponentSize
Now, we add our _componentResized() function, which updates the component size property based on the client width.
_componentResized(entries) { for (const entry of entries) { const rect = entry.target; const rectWidth = entry.target.clientWidth; if (rect && rectWidth) { rect.set('componentXsm', rectWidth >= rect._pixelValues.componentXsm); rect.set('componentSm', rectWidth >= rect._pixelValues.componentSm); rect.set('componentMd', rectWidth >= rect._pixelValues.componentMd); rect.set('componentLg', rectWidth >= rect._pixelValues.componentLg); rect.set('componentXlg', rectWidth >= rect._pixelValues.componentXlg); } } }
Now, we have our component resize function defined, so each component that extends the ComponentSize mixin will respond to changes and update their component size properties.
Demo
Here is a demo of how we can use these breakpoints to create responsive web components.
As you can see, we have several components that are responsive to its own container.
- We see the blog articles component (on the left) change its layout from 3 columns on large screens, to wrap on medium screens, and one column on small/mobile screens.
- On the right, we have a blog sidebar that opens when you click “read more” for an article and is a one column layout. As you drag the sidebar open, you see the content become two columns with the article and the “about the author” information to the right of it. As the sidebar expands, the blog posts section gets smaller and adjusts its layout. So although the screen is full width, the blog articles’ container is small, therefore uses its small layout.
We can achieve this by adding these component breakpoints as attributes on the container element and add needed styles accordingly.
HTML: We add our component attributes. Only include what you need.
<section component-md$=[[componentMd]] component-lg$=[[componentLg]]> <template is="dom-repeat" items="[[blogs]]" as="blog"> <blog-post title="[[blog.title]]" date="[[blog.date]]" description="[[blog.description]]"> </blog-post> </template> </section>
CSS: We can style these elements based on the attribute.
/* Styles for mobile and small component/screen - One column layout */ section blog-post {...} /* Styles for medium component - Two column layout */ section[component-md] blog-post {...} /* Styles for large and xlarge component - Three column layout */ section[component-lg] blog-post {...}
This is how we can style all our components individually. If we need to do things like only display some text/image on large components, we could either:
Observe these properties and update the text in our JavaScript
// create computed function to get header text _getHeaderText(smallText, longText, componentMd) { return componentMd ? longText : smallText; }
Add a condition directly in our html using template dom-ifs
<template is="dom-if" if="[[componentMd]]"> <div class="tags"> <!-- Some tags to only display on larger components --> <i class="fa fa-tags"></i> <ul> <li>Tag 1</li> <li>Tag 2</li> <li>Tag 3</li> </ul> </div> </template>
Or hide/unhide the section using CSS and the component size attributes.
.tags { display: none; } section[component-md] .tags { display: block; }
*Some things to note: For the resize observer to be able to observe changes be sure to add display:block property on the :host selector for each component.
Also, there may be times where you need to force a resize (on Safari for example when component goes from hidden to displayed). In this case you can create a function to force a resize event when displayed.
resize() { window.dispatchEvent(new Event('resize')); }
Conclusion
Media Queries have been very helpful over the years . They’ve pushed responsive web design tremendously, but they don’t allow us to easily modularize our layouts within our components and have them adapt responsively to their own container. Developers and designers have been pushing for an idea called “container queries,” which would allow us to have a responsive layout system for each component, something that would progress current web design. This is how we can create container responsive design within our Polymer applications. This is by no means the only solution but it is a pretty simple solution for using across your application and you can still adopt a similar solution even if you aren’t using Polymer. There are plenty of other resources for solutions to these container size problems and hopefully, in the near future we get our container queries we’ve been hoping for!
Leave a Reply