搜索文章

输入关键词开始搜索

移动端initial-scale导致图表文字过小的问题

思考#程序设计#数据可视化#JavaScript

缘由

投顾部门用到了我们的可视化图表库D3Charts,发现在iOS下文字会变得很小。

经过排查,发现iOS下设置了initial-scale,而Android下没有设置:

<meta
  name="viewport"
  content="width=device-width, initial-scale=0.33, minimum-scale=0.33, maximum-scale=0.33, user-scalable=no,viewport-fit=cover"
/> />

弯路-DPR

因为自己不熟悉移动端,也不熟悉这个组件库,因此一开始走错方向了,以为是DPR的问题,往这个方向搞了好久。

比如做了这些尝试:

1、不缩放,先画出来看看

文字会太小了

2、缩小为三分之一,绘图,不放大

图只有三分之一大小,但是是清晰的

3、缩小为三分之一,绘图,放大

大小正常,但是模糊了

会不会是我缩放设置为div导致的问题?

4、设置#chart > div放大

还是模糊

5、设置canvas放大

不行,外层容器没放大,只显示了部分内容

本质原因

Canvas中的图表是相对尺寸绘制的,而我们传入的文本是用的绝对尺寸px。页面scale到原始的三分之一大小,文字自然就非常小了。

给图表设置dpr是没用的 ,应该给文本、线宽等设置大小才行。

解决方案有多种:

方案一

(最简单)给图表中的文本、线宽等设置过大小,即原始大小*业务方的缩放倍数。

方案二

在绘图的时候,先将绘图的父div元素设置了width=33%,然后再通过scale(3)将其放大3倍(注意设置scale的原点),这样文字就正常了。

transform: scale(3);
transform-origin: 0px 0px;

但是随之而来的问题,就是事件触发的位置不对了,比如手点击某个位置,触发的tooltip是其他数据点的tooltip,这是因为修改了initial-scale,然后我们的canvas内容做了先缩小后放大的操作,导致定位错误。

ZRender捕获事件是通过onmove实现的,因此我们给trigger做了一个拦截,在真正触发事件之前,修改了事件参数,用触发点的x、y除以DPR,这样就能得到真正的视觉上的触发点的坐标了:

// 将chart的this绑定到trigger
const trigger = chart.trigger.bind(chart)
chart.trigger = (eventType, eventParams) => {
  if (eventType === 'mousemove') {
    const dpr = window.devicePixelRatio
    eventParams.offsetX /= dpr
    eventParams.offsetY /= dpr
  }
  trigger(eventType, eventParams)
}

几个问题

为什么图像能自适应大小?

因为图像是按相对尺寸画的,文字是按绝对尺寸画的。

给文字设置fontSize,实际上是算的canvas中的像素值。

第三方组件能不能解决这个问题?

经过测试,并不能。

除非传入rem单位、vw/vh等单位,不过这个本质上和增大字体的px值是一样的。

ZRender哪个版本支持非px的单位?

5以上

为什么只在iOS下会模糊?

原因:initial-scale和dpr对不上。

因为我的iPhone的DPR=2,而设置的initial-scale=3,匹配不上,相当于图片被放大了1.5倍,当然就模糊了。

而我的Android手机的dpr=2.75,比较接近initial-scale的3倍,因此看起来就没那么模糊。

这是打印出来的数据:

iOS:

dpr=2

div:width=1226.58,height=747.42

canvas:width=818,height=498

Android:

dpr=2.75

div:width=1163.145,height=656.10

canvas:width=1066,height=601

当initial-scale设置为和DPR保持一致后,我的iPhone上模糊效果就好多了,剩下的模糊,应该是Canvas本身的模糊问题,得靠另外的方式解决,即手动传入DPR给组件,且我们组件内部也得修改:

1、canvas的width和height乘以dpr倍数

2、canvas的style.width和style.height用原始值,不能乘以dpr倍数

为什么iOS和Android下Canvas模糊程度不一致?

干!经过测试,是iOS16的Bug

我是16.0.3版本就模糊一点,冠豪15.5.0版本就好很多,基本处于可用状态。

Canvas计算时用的是物理像素还是CSS像素?

都不是,计算时用的是Canvas自身的画布大小值。绘制的时候用的才是CSS像素,即逻辑像素。

高分辨率屏幕的注意事项:

🔥关于 canvas 模糊的问题(高清图解) - 掘金

首先让我们先抛弃 css 大小的概念,请记住我们所有的绘图操作都是在 canvas 这个画布大小上进行的;canvas 绘制完成后最终在页面渲染的时候才和 css 大小有关。这时候把 canvas 想象成一张大小固定的照片,把 css 想象成一个容器,不管 css 尺寸如何,这张照片都会铺满整个容器(机制就是这样,没有为啥😒)。所以如果长宽比例相同就会等比缩放;如果长宽比例不同就会拉伸变形;如果大小一样就刚刚好。

绘制流程:

  • Canvas画布根据画布尺寸绘制图形

  • 生成一张图片

  • 将该图片渲染到页面的DIV里面

为什么AntV F2没问题?

有问题的。

用rem等单位能否解决?

可以,本质上和改大font-size的px值是一样的。

canvas是可以通过context.font设置字体的rem等单位的:

context.font = 'bold 20rem serif';

能否用px的情况下,通过缩放canvas解决?

似乎这个可以:

context.scale(dpr, dpr);

这个能保证Canvas内部不模糊。

如果是通过CSS的transform:scale(3)缩放外层HTML元素,那就会糊,因为这相当于拉伸图片(Canvas就理解为图片)。

为什么业务方要设置initial-scale?

是为了开发的便利性

多屏适配方案 - 简书

viewport通过动态设置initial-scale做移动端适配方案,有什么问题?

scale = 1 / dpr;
metaEl.setAttribute('content',  'initial-scale=' + scale + ', maximum-scale=' +  scale + ', minimum-scale=' + scale + ', user-scalable=no');

自动拉伸,相当于不同机型上看到的内容范围是一样的,只是大小不一样。

好处:

  • 简单,只需要根据设计稿的尺寸开发即可,不需要考虑单位转换,

坏处:

  • 完全放弃 px 单位,没法再进行细粒度的调整
  • 在大屏幕上,仅仅是进行放大操作,而不是展示更多内容,丧失大屏幕的优势。

用户使用大屏手机是为了看更多的内容,而不是更大的内容。

这种方案适用于一些活动页面,但是似乎不适用于功能性的产品页面。

https://www.zhihu.com/question/270207480

参考资料

https://segmentfault.com/a/1190000037709547

解决canvas在高清屏中绘制模糊的问题 - 腾讯云开发者社区-腾讯云

🔥关于 canvas 模糊的问题(高清图解) - 掘金

(精)详解meta-viewport标签中的width和initial-scale属性_leman314的博客-CSDN博客_initial-scale

(精)多屏适配方案 - 简书