美文网首页
给 dom 元素添加 onresize 功能

给 dom 元素添加 onresize 功能

作者: 纯爱枫若情 | 来源:发表于2019-11-18 17:28 被阅读0次
imageimage

html 元素自适应

对于我们做前端可视化的人来说,最苦恼的一个地方莫过于,客户需要我们对产品做自适应,特别是还需要做 pc 端的自适应。

一般,面对这个需求的时候,由普通的 html 元素(不包含 canvas)构成的页面,你可以通过对元素的尺寸进行特殊的设置,不采用常用的 px 方案,而是通过设置百分比、vw、em等方式,或者通过媒体查询,或者通过近些年比较流行的 flex 弹性布局 等等方案来解决这个问题。

这么多方案,从中选一种,肯定会适合你的一款。

canvas 自适应的问题

但是对于 canvas 来说,以上方案就捉襟见肘了。

canvas 相当于一个画布,我们朝 canvas 上面添加内容相当于是在画布上绘图。

因此,当 canvas 元素物理尺寸改变的时候,我们画布上的内容,必须要清空了重画。不然,我们绘制到其中的内容,就会被放缩,看起来就会失真了。

而且对于 canvas 来说,它本身有个 width 和 height 来控制绘图区域的尺寸的,一般我们称之为 canvas 画布尺寸。

参考页面:https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement

image.pngimage.png

一般情况下,这个画布尺寸需要设置成和 canvas 元素的实际尺寸等大,这个尺寸也就是通过 canvas 元素的 css 属性 height、width 来控制的。

不然,就会出现尺寸比较难控制,跑偏的情况(这个一般出现在 canvas 尺寸比其 css 控制的尺寸大的情况);或者会出现 canvas 上绘制的内容模糊的情况(一般出现在 canvas 尺寸比其 css 控制的尺寸小的情况)。

第一种情况第一种情况 第二种情况第二种情况

可以看到,这两种情况的显示效果都不太好。

一般的解决思路

因此为了使得我们的 canvas 画布的大小,与他自身物理尺寸的大小相互吻合,我们必须要在 canvas 物理尺寸发生变化的时候,做出一定的应对策略。

这就是所谓的事件监听回调的机制。

但是问题就出现在,如果我们的 canvas 元素,是通过手动设置 px 尺寸,来控制大小的,那么,我们可以顺便在设置 px 的时候,顺手把 canvas 画布的大小改变了,这种逻辑用 js 实现起来很简单。

比如用 window 对象上的 onresize 事件监听窗口的变化,然后在 canvas 上套一个父 div 对象,父 div 对象 css 属性的宽高设置成 100% 这种。然后,当窗口大小发生变化的时候,我们就能调用我们事先写好的回调函数,里面会获取到父 div 的尺寸,设置到 canvas 上去,完成我们的自适应操作。

但是这个地方有个缺点是,如果我们界面上的布局是可以手动变化的,比如有个侧边栏,可以展开收拢,那么此时我们的 onresize 事件就失效了,我们必须要手动管理尺寸的变化,手动调用 onresize 的回调了。

这样还是比较麻烦的。

有没有一劳永逸的方法呢?canvas 自身尺寸变化的时候,为什么就没有监听事件呢?这难道是设计的 bug?

而且一般在实际使用的情况下,我们往往不想采取上面那样做,回调来回调去的,太麻烦了。

有时候,我们有多个元素需要这种自适应处理,我们还得针对每个元素都进行这样的处理,着实不好管理。

理想的解决方案

往往,我们想达到的理想的状况是,我们能通过设置百分比或者 vw 这些方式来设置元素的尺寸。

那到底在元素尺寸变化的时候,有没有办法能监听到变化,并且做出改变呢?

答案当然是有的,就藏在我们的 stackoverflow 上:https://stackoverflow.com/questions/10086693/resize-on-div-element

答案截图答案截图

简单的说,就是通过给元素,设置一个 iframe 子元素。

给 iframe 的宽高设置成 100%,那么他就会跟随着父元素来变化。

而且 iframe 又可以添加 onresize 监听。

自适应小 demo

这个原理说起来简单,但是一下子你还真不一定能想得到。

而下面是我优化后实现这个功能的关键性代码:

function setResize(target, callback) {
  // 创建 iframe
  var iframe = document.createElement('iframe');
  // 改变样式
  iframe.style.cssText = `
    position: absolute; left: 0; top: 0; width: 100%; height: 100%;
        border: 0; margin: 0; display: block; z-index: -999;
  `;

  // 将其设置为传入对象的孩子元素
  target.appendChild(iframe);

  var oldWidth = target.offsetWidth;
  var oldHeight = target.offsetHeight;

  // onresize 回调
  function resizeHandler() {
    var newWidth = target.offsetWidth;
    var newHeight = target.offsetHeight;
    if (oldWidth !== newWidth || oldHeight !== newHeight) {
      callback && callback({ width: newWidth, height: newHeight }, { width: oldWidth, height: oldHeight });
      oldWidth = newWidth;
      oldHeight = newHeight;
    }
  }

  var timer;
  (iframe.contentWindow || iframe).onresize = function() {
    /** 添加防抖机制 **/
    clearTimeout(timer);
    timer = setTimeout(resizeHandler, 20);
  };
}

当然以上代码如果在实际中使用的话,还要考虑兼容性,还需要优化,但是这个功能基本的框架就是这样的。

实际使用的时候 ,传入 dom 对象,传入回调函数:

let canvas = document.querySelector('canvas');
let parentDom = canvas.parentElement;
canvas.width = parentDom.clientWidth;
canvas.height = parentDom.clientHeight;

setResize(parentDom, (newData, oldData) => {
  canvas.width = newData.width;
  canvas.height = newData.height;
  render();
});

html 结构为这样:

<div id="root">
  <canvas></canvas>
</div>

css 样式设置成这样:

html,
body {
  margin: 0;
  padding: 0;
}
#root {
  width: 100vw;
  height: 100vh;
  position: relative;
}
canvas {
  width: 100%;
  height: 100%;
  display: block;
}

实际使用的过程中,拖动窗口的时候,效果如下:

拖动窗口时拖动窗口时

因为添加了防抖逻辑,所以在改变窗口大小的时候,变化稍微有点不太连续,但是实际情况下,也没有人会进行连续变化的操作,所以防抖设置的还是合理的。

接下来,我们不变化窗口的大小,而是单独改变父元素的尺寸,我们会发现,我们的策略同样会生效。

单独改变父元素尺寸单独改变父元素尺寸

如果对这个 dom 感兴趣,可以查看下在线示例:https://dist.coding.me/demo/dynamic%20update%20canvas/

后记

不得不说,这个方法才是最完美的 Polyfill,至少我觉得是这样的,不知道你看完以后觉得如何呢?

我不知道出于什么考量,div 尺寸变化居然不能添加监听。但是显然,有时候,这个需求还是会存在的。

虽然这个方法也不完美,朝 dom 里添加了多余的元素。

但是我感觉,与 canvas 一起用,这个解决方法挺适合的,毕竟都用上 canvas 了,也不会在乎那一点性能损耗吧。

相关文章

  • 给 dom 元素添加 onresize 功能

    html 元素自适应 对于我们做前端可视化的人来说,最苦恼的一个地方莫过于,客户需要我们对产品做自适应,特别是还需...

  • 给dom元素添加类

    在封装公用组件的时候,经常会用到动态给元素添加样式思路:先判断这个dom有没有这个类,没有才给这个dom添加类 往...

  • Vue 中获取 DOM 元素问题

    Vue 获取DOM元素 获取元素 template 部分 js 部分 给元素添加事件 需要用到$nextTick ...

  • 2018-07-24 事件与事件委托机制

    为dom元素添加事件的方式有三种 1:直接在dom元素上,添加属性onclick,然后将一个函数赋值给这个属性即可...

  • js 原生事件委托

    在给dom元素添加异步事件的时候,如果需要给子元素添加事件,有两种办法,1.直接给该子元素添加事件;2.给该子元素...

  • DOM操作

    主要内容: 一些常用的DOM操作:查询元素、创建元素并给元素添加/删除属性、添加/删除元素以及很常用的classL...

  • 原生js操作DOM元素的一些使用

    1、原生js给DOM元素添加一个类名 方法一: 使用DOM.setAttribute("class","类名") ...

  • 给DOM操作添加、移除元素

    1、创建元素。createElement()2、设置内容。createTextNode()3、把它添加到DOM中。...

  • React refs

    1.React.createRef() React.createRef可以给Dom元素添加ref。React.cr...

  • 为dom元素添加resize事件

    为dom元素添加resize事件

网友评论

      本文标题:给 dom 元素添加 onresize 功能

      本文链接:https://www.haomeiwen.com/subject/icmbictx.html