frontend

响应式设计中的 Break Point

由于移动设备(手机,平板电脑等)的流行,越来越多的网站开始使用响应式设计来设计网站。其核心归结为一句话就是,在不同设备上自动适配不同的内容(如下图所示)。而我们为了让网站样式能够支持响应式设计,其中最关键的因素就是 CSS 中的 media queries,media queries 允许我们定义在不同内容和尺寸的设备上的样式。 图片来源:维基百科

如何使用 Media Queries

上面我们说到 media queries 可以帮助我们定义不同尺寸设备上的内容显示,那么我们只需要在我们现有的样式中增加针对特定内容在特定设备或者尺寸的样式即可,例如:

div.container {  
  width: 100%;
}
@media screen only and (min-width: 480px) {
  div.container {
    width: 40%;
  }
}

上面的例子中,我们的定义了只在屏幕(screen only)宽度最小值大于 480px 的时候将宽度从 100% 变成 40%,而这个480px就是我们通常所说的 Break Point 。其中关于更多的 media queries 的属性可以去参考下 Mozilla 的文档1。 不过这个时候大家可能会有所疑问,这里的 480px 真的会预期一样么? image

Break Point 介绍

首先我们要知道,上面的例子中 media queries 针对的是内容的宽度,而通常一个父级元素的宽度是由它包含的子类元素确定的,当然我们也可以制定一个绝对值。当元素的宽度超过屏幕的宽度时我们的内容就会出现水平可滚动的效果,类似下面这种效果:

Parent Width 280px
Child width 380px



同样高度也会出现类似的情况,但是一般从网页交互和用户体验的角度来考虑,我们不会对特定的高度做限定,因为网页的内容是自上而下滚动的,高度可以自由延展。这是可能会想那我定义个 max-width:280px 不就好了嘛。是的,这确实解决了我们的问题,但是随之而来的问题是如果用户改变了他的浏览器的默认字体大小怎么办?在我的上一篇博客中「font-size 的常用长度单位」我介绍了 CSS 中的几种常见单位,其中的单位对于我们做响应式设计中依然至关重要,那具体是怎么表现的呢?

See the Pen breakpoint by xeodou (@xeodou) on CodePen.

在这个例子中当宽度变化时,div 标签的背景透明度将发生改变,我们将从不同维度来看break point 的变化:

  • 更改系统字体大小
  • 缩放屏幕大小
  • 不同浏览器

注:我使用的浏览器版本为 Chrome Version 55.0.2883.95 (64-bit) 和 Safari Version 10.0.2 (12602.3.12.0.1)

正常状态
  • Chrome Chrome 我们可以看到在我拖拽的过程中,随着宽度的缩小三个div标签的背景颜色同时变化,那是因为在不改变系统字体大小并且指定html { font-size: 62.5%; }的时候,在 Chrome 下 480px = 30em = 30rem

  • Safari Safari 我们可以看到其显示的效果和 Chrome 下却有所不同,在宽度小于480px=30em时红色块和绿色块颜色透明度减小,而当宽度小于300px=30rem时蓝色块才开始变化。

更改字体大小
  • Chrome

Chrome 下可以通过设置>高级设置>网页内容 更改字体大小,我们将字体大小从Medium更改到Large,这时 Chrome 的页面内容正常情况下1rem=20px,而当加载html { font-size:62.5%; }后字体大小变成1rem=12.5px
Chrome 1rem=12.5px 我们可以看到其中字体明显变大了,这时候红色色块依然在宽度小于480px的时候颜色变化,而绿色和蓝色色块都同时在30rem=30em=600px时候颜色发生变化。

  • Safari

Safari 下通过点开视图(View)菜单后按住Option键后点击放大或者缩小页面字体2。这时我们将字体像 Chrome 下一样增加字体大小后1rem=19.2px,而当加载html { font-size:62.5%; }后字体大小变成1rem=12px
Safari 文字大小和 Chrome 下一样都明显被放大了,而这时候红色色块也一样在宽度小于480px的时候改变了颜色,而绿色色块在宽度小于30em=12*1.6*30px=576px时候改变了颜色,蓝色块在宽度小于30rem=12*30px的时候颜色发生变化。

定义 Break Point

从上面的这几个例子我们可以看到,就算在同一个设备不同的浏览器下不同的单位也会有不同的表现,虽然px在不同的浏览器甚至是不同的设备中的表现都是一样的,但是当用户希望改变页面的展示形式(字体大小)或者在一个不同的设备上时,他所希望的就是他浏览的网页随着这种改变而改变,所以我们不应该选取px这种非响应式的单位,而且不同浏览器下rem的定义不一致导致我们很难对最终的宽度很有个预测,因此建议选取em做为单位。如果我们在使用 SASS 这种预编译 CSS 时,我们可以使用类似 sass-mq 这种工具库去方便使用各种 media queries,如:

$mq-breakpoints: (
  mobile:  20em,
  tablet:  46.24em,
  desktop: 61.25em,
  wide:    81.25em
);
@import 'mq';
.foo {
  @include mq($from: mobile, $until: tablet) {
    background: red;
  }
  @include mq($from: tablet) {
    background: green;
  }
}

参考链接:

font-size 的常用长度单位

font-size 是网页开发的过程中 CSS 语言针对字体的一个属性,通常我们在设计一个网页时会使用不同的字体大小,可以方便对应 html 中的不同标签,我们一般作为 Heading 在整个项目中去不断的复用这些 Heading。像诸多设计工具一样,font-size 也有不同的单位,设计常用的单位可能有像素(px)厘米(cm)等,而在 CSS 中 font-size 常用的单位有 remempxpt% 这几种。那么 font-size 的不同单位都有什么不同呢?

相对长度单位 emrem%

  • em ,1em 就等于当前元素的字体大小,在印刷媒体时代,通常是用 M 的宽度表示 1em ,其由来由于英文中的 --M 是等宽的,-- 读作 em-dash。但是近年 em 的定义有点改变,因为很多语言中并没有 M(如中文),所以现在表示文字的高度。CSS 中 em 的大小计算是根据当前的元素的 font-size 来计算最终的字体大小,其父级规定的 font-size 对当前元素的真实字体大小影响较为严重。
  • rem 的大小是根据根元素的(root element)的定义的 font-size 来计算最终大小,根元素通常为 HTML 网页中的 htmlbody 标签。
  • % 就是百分比,按照当前的字体大小为 100% 来计算当前元素最终的字体大小。其特点和 em 很像。
<div style="font-size:14px;">  
  <span style="font-size: 1.2em;">1.2em</span>
  <span style="font-size: 120%;">120%</span>
  <span style="font-size: 1.2rem;">1.2rem</span>
</div>  
1.2em 120% 1.2rem

上面的例子中,div规定了其子级的 font-size14px,我们可以看到第一个和第二个 span 的字体大小根据当前的字体大小被改变了。而第三个 span 的字体大小却是根据根元素的字体大小来计算的。

绝对长度单位 ptpx

  • pt(point) 是一个物理上的单位,不会随着不同设备的屏幕进行缩放,对于打印设备比较友好,比如我们在使用 CSS 中的 @medie screen print。1pt大小等于 1/72 英寸。
  • px 是一个视觉角度单位,一个像素代表屏幕的一个点。对于一个 72 PPI(pixels per inch)的屏幕来说,1px 就等于 1pt。会随着不同的屏幕进行缩放但是,其长度会受当前屏幕的 PPI 严重影响。举例来说通常你设置一个 margin 或者 pading 的值你会觉得在不同的屏幕下偏差很多,就是因为屏幕的 PPI 不一样。
<div style="font-size:14px;text-align:center;">  
  <span style="font-size: 16px">16px</span>
  <span style="font-size: 16pt;">16pt</span>
</div>  
16px 16pt

上面我们可以看到,虽然我规定了 div 下的所有元素字体大小都为 14px ,但是其子级的元素都按照其自身的字体的绝对大小显示。

empx

近年来网页设计突出响应式设计和 Mobile First 的网页设计模式,由这两点出发我们也应选择相对单位而非绝对单位。而从适配的角度出发我们也更应该选择 em 而非 px ,对于 em% 而言没有太多差别只不过 em 在语义上更有意义。而且 em 不单单可以用来表示字体大小,也可以在 margin padding 这些凡是涉及距离的属性上使用(% 当然也可以不过在默写情况下如 position: absolute/fixed; 的时候,% 就是相对根元素的百分比了。)。

字体大小

我在上面举例时多次使用font-size:14px,其实一般浏览器默认的字体大小为 16px,所以在网页设计时,一般页面的 body 字体大小不会超过 16px ,body 字体主要是指网页的大段文字的字体大小,推荐范围是14px-16px。很多页面会使用 16px 但是从视觉上来说还是有点偏大,很多著名的前端框架,例如 bootstrap 默认使用 1em=14px 作为主要字体大小。可以点击下面的两个按钮查看这两个字体大小下对于大段文字的视觉影响。


总结

在网页的设计和开发过程中,需要考量的因素有很多,从响应式的角度来考虑应该使用相对计量单位,避免使用绝对单位,包括 w3c 也极力推荐网页开发者使用em做为常用单位;从网页的内容来考虑定义字体的大小,越小的字体代表用户最终阅读到的内容越少。而一般我们在设计开发一个网页项目的起始阶段会定义整个项目的 heading,如:

html {  
  font-size: 62.5%; /* set 1em = 10px; */
}
body {  
  font-size: 1.4em; /* initial font-size: 14px; */
}
h1 { font-size: 4.0em; }  
h2 { font-size: 3.6em; }  
h3 { font-size: 3.0em; }  
h4 { font-size: 2.4em; }  
h5 { font-size: 1.8em; }  
h6 { font-size: 1.5em; }  

参考链接:

文章最先发布在 http://blog.youdaxue.com/front-end/2016/12/22/introduce-font-size-units/

小小bug

最近在写Reactjs,今天在写项目代码的过程中遇到了一个bug,记录一下.

由于项目的css修改,就去修改其中的一个React Component.然后奇怪的事情发生了,component里的Render方法被执行了两次。

代码如下 修改前

var Demo = React.createClass({

  displayName: 'Demo',

  getInitialState: function () {
    return {
      values: []
    };
  },

  handleAdd: function () {
    var values = this.state.values;
    values.push('');
    this.setState({
      values: values
    });
  },

  handleReduce: function (i, e) {
    this.setState({
      values: this.state.values.filter(function (v, _i) {
        return i !== _i;
      })
    });
  },

  handleOnBlur: function (i, e) {
    this.setState({
      values: this.state.values.map(function (v, _i) {
        return i === _i ? e.target.value : v;
      })
    });
  },

  render: function () {
    return React.createElement('div', null,
      React.createElement('button', {
        onClick: this.handleAdd
      }, '+'),
      this.state.values.map(function (v, i) {
        return React.createElement('div', null,
          React.createElement('button', {
            onClick: this.handleReduce(this, i)
          }, '-'),
          React.createElement('input', {
            defaultValue: v,
            onBlur: this.handleOnBlur.bind(this, i)
          })
        );
      }, this)
    );
  }

});

修改后

var Demo = React.createClass({

  displayName: 'Demo',

  getInitialState: function () {
    return {
      values: []
    };
  },

  handleAdd: function (e) {
    e.preventDefault();
    var values = this.state.values;
    values.push('');
    this.setState({
      values: values
    });
  },

  handleReduce: function (i, e) {
    e.preventDefault();
    this.setState({
      values: this.state.values.filter(function (v, _i) {
        return i !== _i;
      })
    });
  },

  handleOnBlur: function (i, e) {
    this.setState({
      values: this.state.values.map(function (v, _i) {
        return i === _i ? e.target.value : v;
      })
    });
  },

  render: function () {
    return React.createElement('div', null,
      React.createElement('button', {
        onClick: this.handleAdd
      }, '+'),
      this.state.values.map(function (v, i) {
        return React.createElement('div', null,
          React.createElement('button', {
            onClick: this.handleReduce(this, i)
          }, '-'),
          React.createElement('input', {
            defaultValue: v,
            onBlur: this.handleOnBlur.bind(this, i)
          })
        );
      }, this)
    );
  }

});

我只是将 div 改成了 label,引起了一个bug,render里的方法被调用了两次。最后调试很久才发现问题出在label这个标签。
HTML里label focus 的话里面的元素也会focus,造成了事件冒泡。当我点击 - 的时候 - button 的事件执行结束以后,传递给 +,结果导致我删除一个输入框以后又回多出一个空白的输入框。 导致一个非常奇怪的bug。
最后的解决办法是在 - 事件执行的最开始添加 e.preventDefault() 函数阻止事件继续传递下去。

这让我想到了之前看到的一句话,在任何事件的执行前都要执行 preventDefault 函数