# 疑难杂症 & 常见问题

# 1 移动端物理像素问题

设备像素比就是设备的物理像素与逻辑像素的比值,公式表达如下:dpr = window.devicePixelRatio。retina屏的手机,dpr值等于2或3,什么意思呢?就是css样式表里写的 1px 映射到物理像素上就有 2px 或 3px。

那么如何实现移动端的 1px

# 伪类 + transform + 媒体查询

不使用原来 border 设置边框, 改用其伪类来模拟 border, 通过媒体查询 和 transform 的 scale 大小来改变边框值。

  .border-1px {
    position: relative;
    box-sizing: border-box;
    width: 100%;
    padding: 10px;
    text-align: center;
  }

  .border-1px:after {
    position: absolute;
    top: 0;
    left: 0;
    content: '';
    box-sizing: border-box;
    border: 1px solid red;
    border-radius: 10px;
    transform-origin: 0 0;
  }

  @media (-webkit-min-device-pixel-ratio: 2) {
    .border-1px::after {
      width: 200%;
      height: 200%;
      transform: scale(0.5);
    }
  }

  @media (-webkit-min-device-pixel-ratio: 3) {
    .border-1px::after {
      width: 300%;
      height: 300%;
      transform: scale(0.333);
    }
  }
  • 优点是所有场景都能满足
  • 缺点是兼容性

# 小数方案 + 媒体查询

通过媒体查询,给不同 dpr 设置不同单位边框值。

  .border-1px {
    text-align: center;
    padding: 10px;
    border-radius: 5px;
    border: 1px solid red;
  }

  @media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border-1px {
      border: 0.5px solid red;
    }
  }

  @media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border-1px {
      border: 0.333px solid red;
    }
  }
  • IOS8+ 苹果系列都已经支持单位小数方案了
  • IOS7及以下和 Android,小数方案会显示为 0px,可以通过 window.devicePixelRatio 来写 Hack
  • 优点就是简单,代码少
  • 缺点就是兼容性差

# viewport + rem

通过 javascript 更改 viewport 的 rem 基准值,这样就可以继续写 px 了。

  • 比如 dpr = 2 时,
  • 比如 dpr = 3 时,
  .border-1px {
    box-sizing: border-box;
    padding: 1rem;
    text-align: center;
    border: 1px solid red;
    border-radius: 0.5rem;
  }
  const viewport = document.querySelector('meta[name=viewport]');
  const dpr = window.devicePixelRatio || 1;
  const scale = (1 / dpr).toFixed(3);
  viewport.setAttribute('content', 'width=device-width,' + 'initial-scale=' + scale + ', maximum-scale=' + scale + ',minimum-scale=' + scale + ', user-scalable=no');

  const docE1 = document.documentElement;
  const fontsize = 10 * (docEl.clientWidth / 320) + "px";
  docEl.style.fontSize = fontsize;
  • 优点就是只适用于新项目,一套代码兼容所有布局
  • 缺点就是老项目修改代价大

# 图片方式

不管通过 border-image 还是 background-image, 修改边框颜色需要换图片,圆角还需单独处理且边缘模糊。

# 插件 postcss-write-svg

项目中使用 PostCss, 然后安装这个插件。

  @svg border-1px-base {
    height: 2px;
    @rect {
      fill: var(--color, gray);
      width: 100%;
      height: 50%;
    }
  }

  .border-1px {
    border: 1px solid transparent;
    border-image: svg(border-1px param(--color red)) 2 2 stretch;
  }

# 2 vue 触发更新问题

1.怎么实现 this._test 改变而不是 this._data.test 改变触发更新呢?

答:其中这中间有一个 代理 的过程。

  _proxy(options.data);

  function _proxy(data) {
    const that = this;
    Object.keys(data).forEach((key) => {
      Object.defineProperty(that, key, {
        configurable: true,
        enumerable: true,
        get: function proxyGetter () {
          return that._data[key];
        },
        set: function proxySetter (val) {
          that._data[key] = val;
        }
      })
    })
  }

本质就是通过 Object.defineProperty 使在访问 this 上的某属性时从 this._data 中读取(写入)。

  1. 能不能将依赖收集中讲到的 dep.addSub(Dep.target) 改成 dep.addSub(new Watcher()) 呢?

为了便于读者理解这部分内容,我将代码做了简化,实际上一个 watcher 对象可能会在多个 Dep 中,并不是每次 addSub 都是一个新的 Watcher 对象,需依赖 Dep.target 进行收集(实际上 Dep.target) 也是通过 Watcher 对象的 get 方法调用 pushTarget 将自身赋值给 Dep.target)。