首頁  >  文章  >  web前端  >  無需媒體查詢的響應式佈局

無需媒體查詢的響應式佈局

Mary-Kate Olsen
Mary-Kate Olsen原創
2024-10-03 16:08:30690瀏覽

建立網頁版面時您使用媒體查詢的頻率如何?我在他們身上花了太多時間!

首先,您花了很多時間嘗試使佈局與設計中的完全一樣。但是,您需要將瀏覽器的大小調整為所有可能的螢幕分辨率,以確保您的頁面在所有解析度上仍然顯示良好。我的意思是不僅要調整寬度,還要調整高度 - 特別是如果您有全高部分。

最終,你的 CSS 會充滿這樣的行:

@media screen and (max-width: 1199px) { /*styles here*/ }
@media screen and (max-width: 1023px) { /*more styles here*/ }
@media screen and (max-width: 767px) { /*another styles here*/ }

這很煩!如果您可以自動包含響應能力,不是會容易得多嗎?當然,您仍然需要提供響應性規則,但無需為數十種螢幕解析度編寫它們。

單位制

關於響應式設計,你需要了解的第一件事是你必須忘記像素。

我知道從一種單位切換到另一種單位可能很困難,但使用像素是過去的聲音。

使用像素作為尺寸單位的最大問題是您無法計算使用者查看您網站的裝置。

現代瀏覽器的預設根字體大小是 16px。這意味著 1rem = 16px。但這並不意味著用戶無法將瀏覽器設定中的值更改為他們想要的任何值。

所以想像使用者的預設瀏覽器字體大小是 24px。但是你將body標籤的字體大小設定為16px。

這是使用者期望看到的內容:

Responsive Layouts Without Media Queries
根字體大小等於 24px

這就是使用者實際看到的:

Responsive Layouts Without Media Queries
根字體大小等於 16px

它尤其會影響有視力問題的人,因此他們將無法輕鬆訪問您的頁面。

當然,他們總是可以縮放您的頁面,但在這種情況下,它會影響其他打開的網站,這些網站可能不應該放大。

順便說一句,Lorem Ipsum 網站是一個非常「好」的壞例子,說明如果您使用像素作為字體、邊距、填充等,頁面看起來會多麼不用戶體驗友好。

如果您不熟悉rem 和vw 等相對單位,您應該在MDN 上查看這篇文章,您可以在其中深入了解CSS 單位和值:https://developer.mozilla.org/en- US/docs /Learn/CSS/Building_blocks/Values_and_units

設定變數

為了更容易建立佈局,我們先設定全域變數。幸運的是,在 CSS 中我們有這樣的機會。由於自訂變數會級聯並從其父級繼承其值,因此我們將在 :root 偽類別上定義它們,因此它們可以應用於整個 HTML 文件。

:root {
  --primary-color: green;
  --primary-font: Helvetica, sans-serif;
  --text-font-size: clamp(1rem, 2.08vw, 1.5rem);
}

看起來很簡單 - 我們定義一個變數名,它必須以雙連字元(--)開頭。然後提供一個變數值,它可以是任何有效的 CSS 值。

然後我們可以使用 var() 函數將這些變數用於文件中的任何元素甚至偽類:

color: var(--primary-color);

例如,我們可以將 --primary-color 變數用於頁面上的所有標題,如下所示:

h1, h2, h3, h4, h5, h6 {
  color: var(--primary-color);
}

由於原色會在頁面上使用相當多的不同元素,因此使用變數而不是每次都編寫顏色本身非常方便。

最後一個變數--text-font-size:clamp(1rem, 2.08vw, 1.5rem)可能看起來很奇怪:clamp是什麼以及它對字體大小變數有什麼作用?

動態字體縮放

clamp() CSS 函數將中間值限制在定義的最小界限和最大界限之間的值範圍內。

您需要提供最小值(上例為 1rem)、首選值 (2.08vw) 和最大允許值 (1.5rem)。

這裡最棘手的部分是設定首選值。它應該採用某些視口相對單位(例如 vw 或 vh)。因此,當使用者調整瀏覽器大小或變更裝置方向時,字體大小將按比例縮放。

我制定了這個公式來計算首選值:

值 = AMValue * remInPx / (containerWidth / 100)

這裡有個解釋,不用驚慌:

AMValue - 算術平均值,介於 rem 允許的最小和最大允許值之間。在我們的範例中,它等於 (1rem 1.5rem) / 2 = 1.25rem

remInPx - 預設大小為 1rem(以像素為單位),取決於您的設計,通常等於 16px

containerWidth - 內容容器區塊的最大寬度(以像素為單位)。我們需要將該值除以 100 以獲得寬度的 1%。在範例中,它等於 960px。

因此,如果將方程式中的參數替換為實數,您將得到:

value = 1.25 \* 16 / (960 / 100) = 2.08

Let’s check how it will scale:

I know it’s not a perfect solution. Besides, we attach again to pixels, when calculating the preferred value. It’s just one of many possible options to make our fonts scale between viewports sizes.

You can use other CSS functions like min() or max(), or create a custom method to calculate the preferred value in the clamp() function.

I wrote an article about dynamic font size scaling, only for pixel units. It’s a bit outdated, but still you might find it helpful:

Dynamic font-size using only CSS3

Ok, enough of the fonts, let’s go further to the layout!

Layout with equal column width

Let’s start with some simple layout with 6 equal columns.

With media queries you need to write a bunch of extra CSS code to handle how they should wrap on different screen sizes. Like this:

/* by default we have 6 columns */
.column {
  float: left;
  width: calc(100% / 6);
}
/* decrease to 4 columns on the 1200px breakpoint */
@media screen and (max-width: 1200px) {
  .column {
    width: calc(100% / 4);
  }
}
/* decrease to 3 columns on the 1024px breakpoint */
@media screen and (max-width: 1024px) {
  .column {
    width: calc(100% / 3);
  }
}
/* finally, decrease to 2 columns for the viewport width less than or equal to 768px */
@media screen and (max-width: 768px) {
  .column {
    width: calc(100% / 2);
  }
}

Woah! That’s a lot of code, I must say! Wouldn't it be better to just make it scale automatically?

And here’s how, thanks to the CSS grid layout:

.row {
  display: grid;
  grid-template-columns: repeat( auto-fit, minmax(10em, 1fr) );
}

All we need to do is to set the parent block of our columns to be displayed as a grid. And then, create a template for our columns, using grid-template-columns property.

This is called RAM technique (stands for Repeat, Auto, Minmax) in CSS, you can read about it in more details here:

RAM Technique in CSS

In that property we use the CSS repeat() function.

The first argument is set to auto-fit, which means it FITS the CURRENTLY AVAILABLE columns into the space by expanding them so that they take up any available space. There’s another value for that argument: auto-fill. To understand the difference between them check this pen:

Also, I highly recommend to read this article from CSS tricks about auto sizing columns in CSS grid: https://css-tricks.com/auto-sizing-columns-css-grid-auto-fill-vs-auto-fit/

The second argument is using another function minmax(), which defines the size of each column. In our example each column should not be less than 10em and should be stretched to the remaining space.

Looks fine, but we have a problem - the number of columns can be bigger than 6!

To make a limit of columns, we need some custom formula again. But hey, it’s still in CSS! And it’s not that scary, basically, you just need to provide a gap for the grid, a minimal column width and the max number of columns.

Here’ the code:

.grid-container {

  /** * User input values. */
  --grid-layout-gap: 1em;
  --grid-column-count: 4;
  --grid-item--min-width: 15em;

  /** * Calculated values. */
  --gap-count: calc(var(--grid-column-count) - 1);
  --total-gap-width: calc(var(--gap-count) * var(--grid-layout-gap));
  --grid-item--max-width: calc((100% - var(--total-gap-width)) / var(--grid-column-count));

  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(max(var(--grid-item--min-width), var(--grid-item--max-width)), 1fr));
  grid-gap: var(--grid-layout-gap);

}

And here’s what we achieve with that:

As you can see, we can use the relative values for the columns min width and gap, which makes this code like the perfect solution. Until they build the native CSS property for that, of course ?

Important notice! If you don't need a gap between columns, you need to set it to 0px or 0em, not just 0 (pure number). I mean you have to provide the units, otherwise the code won’t work.

I’ve found that solution on CSS tricks, so in case you want to dive deeper to how that formula works, here’s the original article about it: https://css-tricks.com/an-auto-filling-css-grid-with-max-columns/

Layout with different column width

The solution above works perfectly for the grids with equal width of the columns. But how to handle layouts with unequal columns? The most common example is a content area with a sidebar, so let’s work with this one.

Here’s a simple markup of the content area along with sidebar:

<section class="content">
  <aside>
    <h2>This is sidebar</h2>
    <section class="grid">
      <div class="grid-item">Grid Item 1</div>
      <div class="grid-item">Grid Item 2</div>
    </section>
  </aside>
  <article>
    <h2>This is content</h2>
    <section class="grid">
      <div class="grid-item">Grid Item 1</div>
      <div class="grid-item">Grid Item 2</div>
      <div class="grid-item">Grid Item 3</div>
      <div class="grid-item">Grid Item 4</div>
    </section>
  </article>
</section>

For the .content section let’s use the flex box layout:

.content {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
}

The flex-wrap property here is important and should be set as wrap in order to force the columns (sidebar and content area) stack under each other.

For the sidebar and content columns we need to set flex properties like grow and basis:

/* Sidebar */
.content > aside {
  border: 1px solid var( - primary-color);
  padding: var( - primary-padding);
  flex-grow: 1;
  flex-basis: 15em;
}

/* Content */
.content > article {
  border: 1px solid var( - primary-color);
  padding: var( - primary-padding);
  flex-grow: 3;
  flex-basis: 25em;
}

The flex-basis property sets the initial size of the flex item. Basically, it’s a minimum width which the flex item should have.

The flex-grow property sets the flex grow factor — similar to the proportion of the flex item compared to the other flex items. It’s a very rough and approximate explanation, to understand better the flex-grow property I highly recommend to read this article from CSS tricks: https://css-tricks.com/flex-grow-is-weird/

So if we set the flex-grow: 1 for the sidebar and flex-grow: 3 for the content area, that means the content area will take approximately three times more space than the sidebar.

I also added the grid section from the previous example to demonstrate that it works inside the flex layout as well.

Here’s what we have in the final result:

Stackable columns

It’s pretty common, when you have a grid layout where text comes next to image on one row and then in reverse order on the next row:

Responsive Layouts Without Media Queries

But when the columns become stacked you want them to be in a specific order, where text comes always before image, but they don’t:

Responsive Layouts Without Media Queries

To achieve that we need to detect somehow when the columns become stacked.

Unfortunately, it’s impossible (yet) to do that with pure CSS. So we need to add some JS code to detect that:

/**
* Detect when elements become wrapped
*
* @param {NodeList} items - list of elements to check
* @returns {array} Array of items that were wrapped
*/
const detectWrap = (items) => {
  let wrappedItems = [];
  let prevItem = {};
  let currItem = {};

  for (let i = 0; i < items.length; i++) {
    currItem = items[i].getBoundingClientRect();

    if (prevItem) {
      let prevItemTop = prevItem.top;
      let currItemTop = currItem.top;

      // if current's item top position is different from previous
      // that means that the item is wrapped
      if (prevItemTop < currItemTop) {
        wrappedItems.push(items[i]);
      }

    }

    prevItem = currItem;

  }

  return wrappedItems;
};

const addWrapClasses = (wrapper, cover) => {
  const items = wrapper.querySelectorAll(":scope > *");

  // remove ".wrapped" classes to detect which items was actually wrapped
  cover.classList.remove("wrapped");

  // only after that detect wrap items
  let wrappedItems = detectWrap(items); // get wrapped items

  // if there are any elements that were wrapped - add a special class to menu
  if (wrappedItems.length > 0) {
    cover.classList.add("wrapped");
  }

};

The function addWrapClasses() accepts two arguments.

The first one is wrapper — it’s a parent element of the items which we should check whether they are wrapped (stacked) or not.

The second argument cover is an element to which we apply a special CSS class .wrapped. Using this class you can change your layout when the columns become stacked.

If you want to apply the .wrapped class directly to the wrapper element you can pass the same element as the second argument.

For better understanding my “wonderful” explanation please see the pen below, hope it will become more clear for you:

You can also use it to detect when the header menu should be collapsed into the burger. You can read about that case in my article here:

An Easy Way to Make an Auto Responsive Menu

Combining all together

Here’s a pen with all the techniques I mentioned in this article combined:

Final thoughts

I’ve used the techniques from this article in my recent project and it worked very well. The web pages look fine on every screen with no need to optimise them manually on multiple breakpoints.

Of course I will be lying if I tell you I didn’t use media queries at all. It all depends on the design and how flexible you can be with modifying page layout. Sometimes it’s much faster and simpler just to add a couple of breakpoints and then fix CSS for them. But I think eventually CSS media queries will be replaced by CSS functions like clamp() which allow developers to create responsive layouts automatically.


If you find this article helpful — don’t hesitate to like, subscribe and leave your thoughts in the comments ?


Read more posts on my Medium blog


Thanks for reading!

Stay safe and peace to you!

以上是無需媒體查詢的響應式佈局的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn