Home >Web Front-end >JS Tutorial >NgSysV.Responsive/Adaptive Design
This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.
Last reviewed: Nov '24
Post 4.2 revealed that if you want your webapp to appear on web searches you must ensure that:
If your software is intended primarily for desktop users, this is a huge nuisance - but that's life. Let's see how you might tackle the problem systematically.
Responsive design uses the "baked-in" capability of CSS styling to test the width of the display device and adjust formatting accordingly. This all happens automatically within the browser - but you've still got to provide explicit instructions about what's to happen at each "breakpoint" (the screen width at which a new width-specific style is to be applied).
The standard CSS styling you've used through this series so far achieves these adaptive effects by using a technique called "media queries". But in this post, I'm going to introduce you to an "open library" called Tailwind. This is tailor-made for responsive styling and has many additional advantages.
Here's an example of Tailwind styling that constrains a centred heading to 95% of screen width on screens up to 768px wide. Above this width, the centered heading is constrained to 60% of the screen width:
<h1> <p>Previously in this series, you've seen styles applied to HTML elements like <p> by adding> <p>The essence of Tailwind is that it provides a system of single-purpose "utility classes", each of which applies a specific set of styles to an element. The class names are chosen judiciously to provide a meaningful and practical expression of styling intentions. The example below styles a <p> element with 4rem padding on all four sides and a background color of light gray.<br> </p> <pre class="brush:php;toolbar:false"><div> <p>Here, in bg-blue-500, bg says that this is a background style, blue sets the background colour to blue and 500 sets the colour "intensity" to a mid-value on a scale of 100 (light) to 900 (dark).</p> <p>This is fine in its way, but the system may only become of interest to you when I tell you that you can make the tailwind utility classes responsive by simply adding a prefix to the style.</p> <p>Tailwind recognizes the following screen-width "breakpoints":</p> <div><table> <thead> <tr> <th>Prefix</th> <th>Screen Size</th> <th>Minimum Width</th> </tr> </thead> <tbody> <tr> <td>sm</td> <td>Small devices</td> <td>640px</td> </tr> <tr> <td>md</td> <td>Medium devices</td> <td>768px</td> </tr> <tr> <td>lg</td> <td>Large devices</td> <td>1024px</td> </tr> <tr> <td>xl</td> <td>Extra large devices</td> <td>1280px</td> </tr> <tr> <td>2xl</td> <td>2x Extra large devices</td> <td>1536px</td> </tr> </tbody> </table></div> <p>A style class such as "bg-gray-200" might thus be made to apply only to screens larger than 640px by specifying it as "sm:bg-gray-200".</p> <p>The "This div has padding on all sides." example above could thus be made to display its paragraph with a blue background on screens with a maximum width of 640px and green on screens larger than this by styling it as follows:<br> </p> <pre class="brush:php;toolbar:false"><p> <p>Because classes to the right take precedence, this makes the default background blue and overrides this with green when the screen is large enough. </p> <p>For a fuller account of the Tailwind system and instructions on how to istall this in your project please see the Tailwind Website.</p> <h3> 3. Adaptive design for Server-side rendered webapps </h3> <p>Responsive design won't help you achieve more drastic effects where the desktop and mobile versions of a webapp are seriously different. Whereas a <strong>responsive design</strong> adjusts a standard pattern"fluidly" to accommodate different screen sizes, an <strong>adaptive</strong> design is prepared to give screen widths tailor-made solutions. </p> <p>Expanding on the "tailoring" theme, you might think of responsive design as creating a single suit made of stretchable fabric that fits anyone. By contrast, adaptive design is like creating multiple tailored suits for different body types.</p> <p>So if, for example, you felt that the mobile customers for your webapp were completely different from your desktop fans, you might want to give each community a tailor-made design (while delivering both under the same URL). </p> <p>Conceptually, the obvious way to express this arrangement would be a displayIsMobile boolean guiding the display of MobileLayout and DesktopLayout components, as follows:<br> </p> <pre class="brush:php;toolbar:false">{#if displayIsMobile} <MobileLayout /> {:else} <DesktopLayout /> {/if}
But you will now ask "How is this displayIsMobile boolean to be initialised?"
When a server receives a browser request for myURL/myPage, the first thing that runs is usually a load() function in a page.server.js file running server-side to provide the initial data for the page. When page.svelte for myPage - also running server-side - receives this data it will want to perform an initial render of its "template" section and send a block of HTML back to the browser. But to do this, it needs a value for displayIsMobile.
If you were running "client-side" then the answer would be simple - use the "window" object to inspect window.width and set displayIsMobile accordingly. But in this case, neither the page.server.js nor the page.svelte file, running server-side as they do, can directly interrogate the client.
One option might be to choose an appropriate default value for displayIsMobile and return a default display. You could then use an onMount() function on the client to inspect its window properties and re-render the default display more appropriately. However, two consequences would follow:
So, if you want to make a proper job of this you've got to find a way of setting displayisMobile appropriately on the server. This way you will send a fully-rendered page to the client as quickly as possible, optimising both performance and SEO.
If you've read Post 3.5 you'll remember that the "headers" that accompany a server request can be used to transmit helpful information. Might the headers for a browser's request for page myURL/myPage say anything useful?
Thankfully, the answer is "yes - they do". For example, the browser-requests user-agent header includes an "Engine and Browser" component that might be used to tell you that the request is coming from a mobile rather than a desktop browser. But the user-agent request header has its roots in computing's dimmest past and its functionality has struggled to balance multiple competing interests.
The chief issue here has been a concern that too precise a description of the user environment (the header also includes details of the user's browser, operating system type and version etc) may be used to identify and track users as they navigate the web. This issue remains unresolved.
Here's a "user-agent" example:
<h1> <p>Previously in this series, you've seen styles applied to HTML elements like <p> by adding> <p>The essence of Tailwind is that it provides a system of single-purpose "utility classes", each of which applies a specific set of styles to an element. The class names are chosen judiciously to provide a meaningful and practical expression of styling intentions. The example below styles a <p> element with 4rem padding on all four sides and a background color of light gray.<br> </p> <pre class="brush:php;toolbar:false"><div> <p>Here, in bg-blue-500, bg says that this is a background style, blue sets the background colour to blue and 500 sets the colour "intensity" to a mid-value on a scale of 100 (light) to 900 (dark).</p> <p>This is fine in its way, but the system may only become of interest to you when I tell you that you can make the tailwind utility classes responsive by simply adding a prefix to the style.</p> <p>Tailwind recognizes the following screen-width "breakpoints":</p> <div><table> <thead> <tr> <th>Prefix</th> <th>Screen Size</th> <th>Minimum Width</th> </tr> </thead> <tbody> <tr> <td>sm</td> <td>Small devices</td> <td>640px</td> </tr> <tr> <td>md</td> <td>Medium devices</td> <td>768px</td> </tr> <tr> <td>lg</td> <td>Large devices</td> <td>1024px</td> </tr> <tr> <td>xl</td> <td>Extra large devices</td> <td>1280px</td> </tr> <tr> <td>2xl</td> <td>2x Extra large devices</td> <td>1536px</td> </tr> </tbody> </table></div> <p>A style class such as "bg-gray-200" might thus be made to apply only to screens larger than 640px by specifying it as "sm:bg-gray-200".</p> <p>The "This div has padding on all sides." example above could thus be made to display its paragraph with a blue background on screens with a maximum width of 640px and green on screens larger than this by styling it as follows:<br> </p> <pre class="brush:php;toolbar:false"><p> <p>Because classes to the right take precedence, this makes the default background blue and overrides this with green when the screen is large enough. </p> <p>For a fuller account of the Tailwind system and instructions on how to istall this in your project please see the Tailwind Website.</p> <h3> 3. Adaptive design for Server-side rendered webapps </h3> <p>Responsive design won't help you achieve more drastic effects where the desktop and mobile versions of a webapp are seriously different. Whereas a <strong>responsive design</strong> adjusts a standard pattern"fluidly" to accommodate different screen sizes, an <strong>adaptive</strong> design is prepared to give screen widths tailor-made solutions. </p> <p>Expanding on the "tailoring" theme, you might think of responsive design as creating a single suit made of stretchable fabric that fits anyone. By contrast, adaptive design is like creating multiple tailored suits for different body types.</p> <p>So if, for example, you felt that the mobile customers for your webapp were completely different from your desktop fans, you might want to give each community a tailor-made design (while delivering both under the same URL). </p> <p>Conceptually, the obvious way to express this arrangement would be a displayIsMobile boolean guiding the display of MobileLayout and DesktopLayout components, as follows:<br> </p> <pre class="brush:php;toolbar:false">{#if displayIsMobile} <MobileLayout /> {:else} <DesktopLayout /> {/if}
I think it's easy enough to see the problems you would encounter parsing this mess!
But there are other options. A recent initiative by Google proposed that browsers should provide a new, much simpler header called sec-ch-ua-mobile. This contains a simple string that tells you whether or not the browser expects a mobile response (see Sec-CH-UA-Mobile for details).
However, while the sec-ch-ua-mobile header is now available from Chrome and Edge, other browsers won't necessarily support the initiative. In any case, the sec-ch-ua-mobile header doesn't give you enough detail to refine your response and serve, say, an explicit "tablet" version.
This is all very tedious, but it may be enough for you to conclude that you're happy to go with sec-ch-ua-mobile as the first port of call and the user-agent as a fallback. In that case, here's some code to give a page.svelte file an displayIsMobile variable.
Confusingly it starts with a new type of Svelte file called a hooks.server.js file.
While you might put the code to set displayIsMobile for a page.svelte file in a load() function, not every page.svelte page will have one of these. And even if it did (and you can always create one, of course), you'd find you had to duplicate the displayIsMobile code in all load() functions.
By contrast, the hooks.server.js file is a sort of "super" load() function that Svelte launches for every request submitted to the server. It runs before any other activity is executed. This makes it the perfect place to inspect the sec-ch-ua-mobile header and create a value for displayIsMobile.
The code below shows how displayIsMobile might be constructed by a hooks.server.js file. It also shows how this value might be communicated back to the expectant page.svelte file.
<h1> <p>Previously in this series, you've seen styles applied to HTML elements like <p> by adding> <p>The essence of Tailwind is that it provides a system of single-purpose "utility classes", each of which applies a specific set of styles to an element. The class names are chosen judiciously to provide a meaningful and practical expression of styling intentions. The example below styles a <p> element with 4rem padding on all four sides and a background color of light gray.<br> </p> <pre class="brush:php;toolbar:false"><div> <p>Here, in bg-blue-500, bg says that this is a background style, blue sets the background colour to blue and 500 sets the colour "intensity" to a mid-value on a scale of 100 (light) to 900 (dark).</p> <p>This is fine in its way, but the system may only become of interest to you when I tell you that you can make the tailwind utility classes responsive by simply adding a prefix to the style.</p> <p>Tailwind recognizes the following screen-width "breakpoints":</p> <div><table> <thead> <tr> <th>Prefix</th> <th>Screen Size</th> <th>Minimum Width</th> </tr> </thead> <tbody> <tr> <td>sm</td> <td>Small devices</td> <td>640px</td> </tr> <tr> <td>md</td> <td>Medium devices</td> <td>768px</td> </tr> <tr> <td>lg</td> <td>Large devices</td> <td>1024px</td> </tr> <tr> <td>xl</td> <td>Extra large devices</td> <td>1280px</td> </tr> <tr> <td>2xl</td> <td>2x Extra large devices</td> <td>1536px</td> </tr> </tbody> </table></div> <p>A style class such as "bg-gray-200" might thus be made to apply only to screens larger than 640px by specifying it as "sm:bg-gray-200".</p> <p>The "This div has padding on all sides." example above could thus be made to display its paragraph with a blue background on screens with a maximum width of 640px and green on screens larger than this by styling it as follows:<br> </p> <pre class="brush:php;toolbar:false"><p> <p>Because classes to the right take precedence, this makes the default background blue and overrides this with green when the screen is large enough. </p> <p>For a fuller account of the Tailwind system and instructions on how to istall this in your project please see the Tailwind Website.</p> <h3> 3. Adaptive design for Server-side rendered webapps </h3> <p>Responsive design won't help you achieve more drastic effects where the desktop and mobile versions of a webapp are seriously different. Whereas a <strong>responsive design</strong> adjusts a standard pattern"fluidly" to accommodate different screen sizes, an <strong>adaptive</strong> design is prepared to give screen widths tailor-made solutions. </p> <p>Expanding on the "tailoring" theme, you might think of responsive design as creating a single suit made of stretchable fabric that fits anyone. By contrast, adaptive design is like creating multiple tailored suits for different body types.</p> <p>So if, for example, you felt that the mobile customers for your webapp were completely different from your desktop fans, you might want to give each community a tailor-made design (while delivering both under the same URL). </p> <p>Conceptually, the obvious way to express this arrangement would be a displayIsMobile boolean guiding the display of MobileLayout and DesktopLayout components, as follows:<br> </p> <pre class="brush:php;toolbar:false">{#if displayIsMobile} <MobileLayout /> {:else} <DesktopLayout /> {/if}
So now, displayIsMobile is sitting in the event object for the browser request. This event is a complex object constructed by SvelteKit to represent the current request. It contains properties such as:
As you'll imagine, since event will now be available everywhere it might be needed, event.locals is exactly what you need to provide a home for displayIsMobile.
The form of the {event, response} argument to handle() may perplex you. This is an example of "destructuring" syntax. This enables you to directly extract specific properties from an object without referencing the object itself. Imagine there's a super-object args that contains event and response as properties. Then instead of using the conventional
User-Agent: Mozilla/4.9 Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
"destructuring syntax" allows you to write this as
// src/hooks.server.js export async function handle({ event, resolve }) { let displayIsMobile; console.log("event.request.headers['sec-ch-ua-mobile']: ", event.request.headers.get('sec-ch-ua-mobile')); // First, try to get the mobile flag from the 'sec-ch-ua-mobile' header. This is a string header // and its value is '?1' if the user agent is a mobile device, otherwise it is '?0'. if (event.request.headers.get('sec-ch-ua-mobile') !== undefined) { displayIsMobile = event.request.headers.get('sec-ch-ua-mobile') === '?1' ? true : false; } else { // Otherwise, try the 'user-agent' header. For robust mobile detection, you might consider using // the ua-parser-js library. It provides consistent results across various edge cases. if (event.request.headers.get('user-agent') !== undefined) { displayIsMobile = event.request.headers.get('user-agent').toLowerCase().includes('mobile'); } else { displayIsMobile = false } } // Put displayIsMobile into event.locals. This is an object provided by SvelteKit that is specific to a // particular browser request and which is acessible in every page and layout. In brief, event.locals lets // you pass data throughout the lifecycle of a request in SvelteKit. It provides a convenient way to share // computed values or state without needing to repeat logic or fetch data multiple times. event.locals.displayIsMobile = displayIsMobile; // Proceed with the request. In SvelteKit, resolve(event) is crucial for handling the request lifecycle. // It processes the current request and generates the final response that will be sent back to the client. const response = await resolve(event); return response; }
Essentially, this is a way of referencing properties (args.event etc) of an object args without knowing the parent object's name (args). This leads to tighter, more resilient code.
Anyway, with all that said, with displayIsMobile now sitting in the event object for the browser request, the obvious thing to do is to use a load() function in a page.server.js file to dig it out and return it to page.svelte.
function handle(args) { const event = args.event; const resolve = args.resolve; // ... (code referencing variables "event" and "resolve") }
So here, finally, is the very simple page.svelte file to deliver an adaptive page
function handle({ event, resolve }) { // ...(code referencing variables "event" and "resolve") }
I hope you enjoyed that!
In summary, the full sequence is:
Once the page is hydrated, reactivity is purely a client-side concern. A SvelteKit {#if popupIsVisible in the template section of your code will have become a compiled function that toggles DOM elements based on popupIsVisible.
The above is the detailed content of NgSysV.Responsive/Adaptive Design. For more information, please follow other related articles on the PHP Chinese website!