Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

MartinJ
MartinJ

Posted on • Edited on

NgSysV2-4.4: Responsive/Adaptive Design

This post series is indexed atNgateSystems.com. You'll find a super-useful keyword search facility there too.

Last reviewed: Mar '25

1. Introduction

Post 4.2 revealed that if you want your webapp to appear on web searches you must ensure that:

  • Your webapp works well when viewed on the small screen of a mobile phone and
  • All the content you want to be indexed by search engines is visible on the mobile version.

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.

2. Responsive design using Tailwind

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:

<h1class="w-[95%] md:w-[60%] mx-auto text-center">  Centered Heading</h1>
Enter fullscreen modeExit fullscreen mode

Previously in this series, you've seen styles applied to HTML elements like<p> by addingstyle="...." andclass="...." qualifiers. Within astyle="...." qualifier you've seen reference to CSS properties such aswidth andmargin that give the browser instructions on how you want the HTML element formatted. Theclass="...." qualifier lets you reference a "tag" that you've created to define a particular collection of CSS properties that you want to use repeatedly. This arrangement keeps your code compact and also simplifies maintenance.

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.

<divclass="p-4 bg-gray-200">  This div has padding on all sides.</div>
Enter fullscreen modeExit fullscreen mode

Here, inbg-blue-500,bg says that this is a background style,blue sets the background colour to blue and500 sets the colour "intensity" to a mid-value on a scale of 100 (light) to 900 (dark).

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.

Tailwind recognizes the following screen-width "breakpoints":

PrefixScreen SizeMinimum Width
smSmall devices640px
mdMedium devices768px
lgLarge devices1024px
xlExtra large devices1280px
2xl2x Extra large devices1536px

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".

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:

<pclass="p-4 bg-blue-500 sm:bg-green-500">This paragraph has a blue background on small screens and a green background on larger screens.</p>
Enter fullscreen modeExit fullscreen mode

Because classes to the right take precedence, this makes the default background blue and overrides this with green when the screen is large enough.

You might be interested to know that, "beneath the hood", your project is still using CSS "media queries", but you don't need to worry about this now. Tailwind is generating these automatically.

For a fuller account of the Tailwind system and instructions on how to install this in your project, please see theTailwind Website.

3. Adaptive design for Server-side rendered webapps

Responsive design won't help you achieve more drastic effects where the desktop and mobile versions of a webapp are seriously different. Whereas aresponsive design adjusts a standard pattern"fluidly" to accommodate different screen sizes, anadaptive design is prepared to give screen widths tailor-made solutions.

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.

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).

Conceptually, the obvious way to express this arrangement would be adisplayIsMobile boolean guiding the display ofMobileLayout andDesktopLayout components, as follows:

{#if displayIsMobile}  <MobileLayout />{:else}  <DesktopLayout />{/if}
Enter fullscreen modeExit fullscreen mode

But you will now ask "How is thisdisplayIsMobile boolean to be initialised?"

When a server receives a browser request formyURL/myPage, the first thing that runs is usually aload() function in a+page.server.js file runningserver-side to provide the initial data for the page. When+page.svelte formyPage -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 fordisplayIsMobile.

If you were running "client-side" then the answer would be simple - use the "window" object to inspectwindow.width and setdisplayIsMobile 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 fordisplayIsMobile and return a default display. You could then use anonMount() function on the client to inspect itswindow properties and re-render the default display more appropriately. However, two consequences would follow:

  • the re-rendering of the initial display would generate an unpleasant "flicker" effect on the client device as each page starts up and then re-renders.
  • SEO would likely be seriously damaged because web-crawlers (which may not always execute JavaScript) might not see the correct content.

So, if you want to make a proper job of this you'vegot to find a way of settingdisplayisMobile 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 readPost 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 pagemyURL/myPage say anything useful?

Thankfully, the answer is "yes - they do". For example, the browser-requestsuser-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 theuser-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:

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
Enter fullscreen modeExit fullscreen mode

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 calledsec-ch-ua-mobile. This contains a simple string that tells you whether or not the browser expects a mobile response (seeSec-CH-UA-Mobile for details).

However, while thesec-ch-ua-mobile header is now available from Chrome and Edge, other browsers won't necessarily support the initiative. In any case, thesec-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 withsec-ch-ua-mobile as the first port of call and theuser-agent as a fallback. In that case, here's some code to give a+page.svelte file andisplayIsMobile variable.

Confusingly it starts with a new type of Svelte file called ahooks.server.js file.

While youmight put the code to setdisplayIsMobile for a+page.svelte file in aload() 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 thedisplayIsMobile code inallload() functions.

By contrast, thehooks.server.js file is a sort of "super"load() function that Svelte launches forevery request submitted to the server. It runs before any other activity is executed. This makes it the perfect place to inspect thesec-ch-ua-mobile header and create a value fordisplayIsMobile.

The code below shows howdisplayIsMobile might be constructed by ahooks.server.js file. It also shows how this value might be communicated back to the expectant+page.svelte file.

// src/hooks.server.jsexportasyncfunctionhandle({event,resolve}){letdisplayIsMobile;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.constresponse=awaitresolve(event);returnresponse;}
Enter fullscreen modeExit fullscreen mode

So now,displayIsMobile is sitting in theevent object for the browser request. Thisevent is a complex object constructed by SvelteKit to represent the current request. It contains properties such as:

  • event.request: This is the original Request object, containing details like the HTTP method (GET, POST, etc.), headers, URL, and body.
  • event.locals: A place to make this data available throughout the request's subsequent lifecycle.

As you'll imagine, sinceevent will now be available everywhere it might be needed,event.locals is exactly what you need to provide a home fordisplayIsMobile.

The form of the{event, response} argument tohandle() 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-objectargs that containsevent andresponse as properties. Then instead of using the conventional

functionhandle(args){constevent=args.event;constresolve=args.resolve;// ... (code referencing variables "event" and "resolve")}
Enter fullscreen modeExit fullscreen mode

"destructuring syntax" allows you to write this as

functionhandle({event,resolve}){// ...(code referencing variables "event" and "resolve")}
Enter fullscreen modeExit fullscreen mode

Essentially, this is a way of referencing properties (args.event etc) of an objectargs without knowing the parent object's name (args). This leads to tighter, more resilient code.

Anyway, with all that said, withdisplayIsMobile now sitting in theevent object for the browser request, the obvious thing to do is to use aload() function in a+page.server.js file to dig it out and return it to+page.svelte.

// src/routes/+page.server.jsexportfunctionload({locals}){//Provide a load function that returns the displayIsMobile flag to its associated +page.svelte filereturn{displayIsMobile:locals.displayIsMobile};}
Enter fullscreen modeExit fullscreen mode

So here, finally, is the very simple+page.svelte file to deliver an adaptive page

// src/routes/+page.svelte<script>exportletdata;</script><p>In+page.svelte:mobileis{data.displayIsMobile}</p>{#ifdata.displayIsMobile}<p>You're on a mobile device.</p>{:else}    <p>You'reonadesktopdevice.</p>{/if}
Enter fullscreen modeExit fullscreen mode

I hope you enjoyed that!

In summary, the full sequence is:

  1. The Sveltekit server fields the browser'smyURL/myPage request and launches the project'shooks.server.js file. Here, the request headers are retrieved, an appropriatedisplayIsMobile value determined, and the result tucked away in the Sveltekitevent object.
  2. Theload() function in the+page.server.j file for themyPage route retrievesdisplayIsMobile fromevent and returns it to+page.svelte
  3. The+page.svelte file retrieves thedata.displayIsMobile value and uses this in its template section to generate appropriate HTML.
  4. Sveltekit constructs scripts for the browser to add interactive behaviour. Tailwind references will already have been converted into CSS media queries during the page build.
  5. The browser receives this HTML, "hydrates" it with the Sveltekit scripts and renders it on the client device as directed by the media queries.

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 onpopupIsVisible.

4. Testing your Design

You may be wondering how you can test the results of a responsive/adaptive design. It's easy to see how the desktop version looks when this is the platform you're using to develop it, but how will you check it out on an iPad or an iPhone? Do you really have to deploy your webapp and beg/borrow/steal different devices to try it out?

Of course, you don't - step forward, once more, that Swiss Army Knife of testing tools, the Google Inspector!

Launch your webapp withnpm run dev and navigate to the page you want to test. Now open the Inspector and look for an icon at the left-hand end of the menu bar that looks a bit like a small screen embedded in a larger one. When you mouse over the icon, it should display a "Toggle device toolbar" tooltip.

Click this and be amazed as the "desktop" view of your webapp page is replaced by an image showing how it will appear on a much smaller screen. Controls at the top of this image let you set the dimension of this "smaller screen". You can do this implicitly by either using a pull-down list to select a particular device from a list of popular models or by explicitly selecting "responsive" and specifying "width" and "height" settings. The image below shows the Inspector demonstrating how a 'dev.to' post page would appear on an iPhone SE.

Image showing dev.to page rendered for display on an iPhone SE by the google insoector

Note that I've optimised the display for this particular target device by using the Inspector's "three-dot" options menu to dock the tools to the right-hand side of the screen.

There's a great deal more to be said about this facility, but now that I've got you started, it's probably best if I now hand you over to Google's own docs atSimulate mobile devices with device mode to provide more details. I think you're going to love using this utility.

5. 'Zooming' and its implications for webapp design

Browsers on both desktop and mobile devices provide the ability to "zoom" the display so that it appears that it is being viewed under a magnifying glass. In Windows, you zoom in with a "ctrl +" key (and zoom back out again with ctrl -). On a mobile device, you use a two-finger "spreading" action. These facilities are invaluable to anyone with sight issues.

Your first thought might be that the desktop browser creates the zoom effect by increasing the font size. The arrangement is actually much more subtle - the browserreduces its estimate of the pixel width of the screen when ctrl + is pressed. As a result, the font size (which is unchanged) is now seen to cover a larger physical screen area and thus appears bigger. Likewise, widths, heights, margins, etc, expressed in pixels (or pixel derivatives such as em or rem) are zoomed, so container positioning and sizing change proportionately.

The only measurement units that arenot affected are those expressed in percentages of screen height and width - ievh andvw. If you usevh to fix both the tailwindtext size of a<p> element and the height of its container, pressing ctrl + won't have the slightest effect on your display. What happens if thetext size of the<p> is switched torem units but the container height continues to be specified invh? Why, pressing ctrl + continues still has no effect on the container, but the line-height of the text (usually 1.5 font-size) now slowly expands. The effect is that the bottom and right-hand limits of the container cut off the expanding text. If you continue to press ctrl +, the text will eventually disappear altogether!

Note that on mobile devices, zooming is handled entirely by the OS through a universal "digital magnifier" effect. You don't have the fine control described above and which you can use to "fix" the size of selected screen elements.

Zooming in onone page in a desktop browser has no effect onother opened browser pages, and the zoom-in setting will be remembered and re-applied to that page on a future visit. Additionally, however, Windows configuration settings enable you to set a "scale" factor that will affectall pages (with the option of adding further zooming with ctrl+, if required)

Anyway, the overall effect of these considerations is that, if your desktop webapp uses pixel-based units, it's not possible to predict the user's initial impression of the page layout, and a zoomed screen may not provide the best initial impression of your webapp.

Here's an example of a webpage (one of mine, actually) that neglected to consider the effects of "zooming":

Badly-designed page after zooming

Zooming is meant to be a help, not a hindrance. This website design is a disaster for anyone looking for visual assistance. I've fixed my page now - make sure you've not fallen into the same trap

Another issue is that as the user zooms into a screen and reduces the browser's estimate of its pixel width, screen-width "breakpoints" may be triggered. This is not necessarily bad—if the screen has been zoomed to the point that it's being treated like a mobile phone, the new layout may be entirely appropriate. But it's disconcerting.

One way to avoid these problems might be to use onlyvh andvw units throughout your webapp, but this would leave sight-impaired users without a zoom facility.

As a designer, your response to these issues will depend upon the circumstances. In theNgateSystems website, for example, the heading and the page's containers are all invariant because they usevh/vw sizing for both positioning and font sizes (using fractionalvh specifications) and are considered to be sufficiently large to be usable without re-sizing. But the font size of the index display itself is rem-based and thus zooms. You might also notice that the design switches to a mobile sm layout when zoomed in sufficiently closely. Everything is a compromise, and I think this is a reasonable one. However, inspection of complex sites such as Amazon suggests that all elements are sized in zoomable rem units - the site thus behaves on a laptop exactly as it would on a touch-screen device. Clearly, the sort of arrangements described above are only seen to be practical in simple situations.

In conclusion, let me also say that "zoom" support is only one way in which your website design can (and should) assist users with physical challenges. This subject will be covered in a future post on the ARIA (Accessible Rich Internet Applications) specification.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I cut my first commercial code (Telcomp) at Time Sharing Ltd in the UK. Now, long retired, I remain fascinated by software development technologies.
  • Location
    Cumbria, UK
  • Work
    Technical writer and Javascript enthusiast
  • Joined

More fromMartinJ

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp