给 dom 元素添加 onresize 功能

image
image

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.png
image.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 了,也不会在乎那一点性能损耗吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343