REMux:一种响应式网页设计实验性方法

Avatar of Chris Coyier
Chris Coyier 发表

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200美元免费额度!

以下是 Dirk Lüth 的一篇客座文章。Dirk 对响应式布局有独特的见解。它使用媒体查询断点来像我们习惯的那样重新排列元素,但流体列宽以rem单位而不是百分比设置,并且font-size根据屏幕宽度使用JavaScript动态调整。结果产生了一种“缩放”效果,向上或向下,直到下一个媒体查询生效。我让他来详细介绍一下。

简介

我相信我们都同意,响应式网页设计在过去几年中一直是最重要的主题之一,并且随着移动设备的增长将继续发展。作为一名资深的前端和后端开发人员,我对公司内部的研究和开发有着浓厚的兴趣,我负责评估RWD等技术。每当我收到一个指向全新CSS网格系统的链接时,我都会越来越怀疑。它们对我来说感觉“不对劲”,但我不知道为什么。

然后,我偶然发现了一篇由Ian Yates撰写的很棒的文章,名为“960px之外的生活:为大屏幕设计”,它向我介绍了“屏幕空间”这个术语。在此之前,我使用CSS中的rem单位进行了一些更深入的研究,这是一个幸运的巧合。突然间,我知道哪里感觉不对劲了。

当谈到RWD时,我们主要讨论的是低于我们布局目标宽度的设备。但是更大的屏幕呢?你们大多数人都会同意,目标宽度为960px的非RWD网站在这种屏幕上看起来有点奇怪或显得格格不入。当我们谈论人们使用60英寸电视访问我们的网站时,情况变得更加明显。当然,这些电视机很可能仍然只有全高清分辨率。但请记住,坐在电视机前的人可能距离屏幕至少4米/10英尺。

现状

无论我们先做移动端、平板电脑端还是桌面端——我们大多数人最终都会至少有3个具有不同布局的媒体查询断点。或者我们会使用一个网格系统,它会自动更改我们网站内容元素的构成。或者两者兼而有之。如果我们要支持越来越多的不同分辨率和不同的观看情况,这两种方法都有其缺点。

  • 更多断点 = 更多布局 = 更多工作
  • 难以平衡元素的灵活性和比例
  • 元素重新堆叠时的卡顿感
  • 受用于填充视口内容量的限制
  • 网格的艺术不仅仅是Photoshop中的某些指南

无论我们选择哪种最合适的方式:**实际内容(包括每个元素)都不会按比例缩放。**

概念验证

我想到的是一个完全基于font-sizerem驱动布局。如果这个想法有效,我可以通过简单地更改<html>元素上的font-size轻松缩放所有内容。这个概念将解决最大的挑战:**布局在其边界内几乎完美地缩放。**

请记住,rem仅在IE9+和所有其他当前浏览器中受支持。对于旧版浏览器,可以使用基于px的回退。请参见下面的示例混合。

我开始使用我自己的网站进行一些概念验证工作。在我开发了一些将像素单位(直接从布局中获取)转换为基于我决定使用的14px font-sizerem单位的LESS混合之后(参见示例),静态版本运行得非常好。

LESS混合

这里有两种类型的混合:一种用于单个参数属性(如font-size),另一种用于具有多个参数的属性(如border)。两者都依赖于必须定义的两个LESS变量。

@remuxFontsize: 14px;  // base font size
@remuxFallback: false; // switch to include px fallback

.remux(font-size, @value) {
  @processingRem: ((round((@value / @fontsize-base) * 10000000)) / 10000000);

  .result(@value, @fallback) when (@fallback = true) {
    font-size: ~"@{value}px";
    font-size: ~"@{processingRem}rem";
  }
  .result(@value, @fallback) when (@fallback = false) {
    font-size: ~"@{processingRem}rem";
  }

  .result(@value, @remuxFallback);
}

.remux(border, @value) {
  @processingRem: ~`(function() { var base = parseFloat("@{remuxFontsize}"); return "@{value}".replace(/[\[\]]/g, '').replace(/([0-9]*[.]{0,1}[0-9]*px)/gi, function() { return ((Math.round((parseFloat(arguments[0]) / base) * 10000000)) / 10000000) + 'rem'; }); }())`;

  .result(true) {
    border: @value;
    border: @processingRem;
  }
  .result(false) {
    border: @processingRem;
  }

  .result(@remuxFallback);
}

使用混合非常简单。

/* Instead of this... */
font-size: 16px;
/* You use this */
.remux(font-size, 16px);

/* Instead of this... */
border: 7px solid #000;
/* You use this */
.remux(border, ~"7px solid #000");

当我将网站的CSS切换为使用rem时,我已经能够通过开发人员工具更改<html>元素的font-size,并看到网站几乎完美地缩放了!

接下来我需要一个解决方案,以便在“resize”和“orientationchange”事件上通过JavaScript动态计算此值。基本计算非常简单。

;(function(window, document, undefined) {
  'use strict';
  
  var targetLayoutWidth = 980,
    baseFontsize      = 14,
    htmlElement       = document.getElementsByTagName('html')[0],
    documentElement   = document.documentElement;
  
  function updateFontsize() {
    var currentFontsize = baseFontsize * (documentElement.offsetWidth / targetLayoutWidth);
      
    htmlElement.style.fontSize = currentFontsize + 'px';
  }
    
  window.addEventListener('resize', updateFontsize, false);
  window.addEventListener('orientationchange', updateFontsize, false);
  
  updateFontsize();
}(window, document));

在设置结果font-size时,我意识到浮点数在浏览器四舍五入时会导致问题(参见示例)。因此,我最终不得不对font-size进行floor操作,这消除了任何四舍五入故障,但也使缩放不太“流畅”。不过,对我来说,它仍然感觉绰绰有余(参见示例)。

接下来,我在我的iPad 2上进行了一些扩展测试,并将HTML视口设置为“device-width”。我承认,当不仅在初始页面加载后一切正常,而且捏合缩放和设备方向更改也按预期工作时,我有点惊讶。

实现布局

现在我能够无限缩放我的布局,我开始实现断点。仍然需要断点,因为将font-size缩小到零或放大到无限大实际上没有意义(尽管在技术上是可能的)。

我开始通过确定需要什么来规划未来布局对象的结构。这导致了以下JavaScript对象结构。

var layouts = {
  'ID': {
    width:      980,            // designs target width
    base:       14,             // designs base font-size
    min:        10,             // minimum font-size
    max:        18,             // maximum font-size
    breakpoint: 980 * (10 / 14) // minimum breakpoint
  }
}

现在可以定义布局了,我必须将它们添加到更新函数中,这比我想象的影响要大一些(参见示例)。

;(function(window, document, undefined) {
  'use strict';

  var htmlElement     = document.getElementsByTagName('html')[0],
    documentElement = document.documentElement,
    layouts = {
      mobile: {
        width:      420,
        base:       14,
        min:        10,
        max:        23,
        breakpoint: 420 * (10 / 14)
      },
      desktop: {
        width:      980,
        base:       14,
        min:        10,
        max:        18,
        breakpoint: 980 * (10 / 14)
      }
    },
    state = {
      size:   null,
      layout: null
    };

  function updateFontsize() {
    var width, id, layout, current, size;
  
    width = documentElement.offsetWidth;
  
    for(id in layouts) {
      if(layouts[id].breakpoint && width >= layouts[id].breakpoint) {
        layout = id;
      }
    }
  
    if(layout !== state.layout) {
      state.layout = layout;
  
      htmlElement.setAttribute('data-layout', layout);
    }
  
    current = layouts[state.layout];
  
    size = Math.max(current.min, Math.min(current.max, Math.floor(current.base * (width / current.width))));
  
    if(size !== state.size) {
      state.size = size;
  
      htmlElement.style.fontSize = size + 'px';
    }
  }
  
  window.addEventListener('resize', updateFontsize, false);
  window.addEventListener('orientationchange', updateFontsize, false);
  
  updateFontsize();
}(window, document));

我在Chrome、Safari、FF17+和IE9(REMux应该可以工作的地方)上测试了这个原型,并且它确实可以工作。

查看实际效果

前面已经提到,REMux 在我自己的网站上被大量(滥)用。除此之外,CodePen上还有一些Pen,它们在整篇文章中都有链接。此外,还有一个Pen展示了我最终的结果


我希望很快就能准备好一些文档——但代码本身并不难阅读。

接下来是什么?

REMux 运行得如此出色,以至于我决定将其作为我不断增长的JavaScript库的一部分,该库可以在GitHub上找到。在过去几周里,我还添加了一些此基本文章中缺少的其他功能。

  • AMD兼容(require.js)
  • addLayout()方法,用于在不更改脚本的情况下添加布局
  • 像素密度、缩放级别检测、字体大小、总数和图像的比率
  • getState()方法,将返回所有大小和比率
  • 发出事件ratiochangelayoutchange,并将当前状态作为参数传递
  • 用于重复输入事件的防洪机制,以提高性能
  • 可以通过对象扩展机制进行扩展

REMux 的独立版本可以在我的GitHub存储库的“packages”文件夹中找到,大小约为4kb(压缩并gzipped)。它不依赖于jQuery或任何其他大型库。它对我的库中其他组件的依赖项都包含在包中。我的JavaScript库的所有组件都是根据MIT和GPL许可证双重许可的。

随时下载并在您的项目中使用它,但请记住提供反馈并报告错误,以便我能够使其变得更好!