首页 >web前端 >css教程 >无需媒体查询的响应式布局

无需媒体查询的响应式布局

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-10-03 16:08:30727浏览

构建网页布局时您使用媒体查询的频率如何?我在他们身上花了太多时间!

首先,您花了很多时间尝试使布局与设计中的完全一样。但是,您需要将浏览器的大小调整为所有可能的屏幕分辨率,以确保您的页面在所有分辨率上仍然显示良好。我的意思是不仅要调整宽度,还要调整高度 - 特别是如果您有全高部分。

最终,你的 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