移动端initial-scale导致图表文字过小的问题
缘由
投顾部门用到了我们的可视化图表库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像素,即逻辑像素。
高分辨率屏幕的注意事项:
首先让我们先抛弃 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在高清屏中绘制模糊的问题 - 腾讯云开发者社区-腾讯云
(精)详解meta-viewport标签中的width和initial-scale属性_leman314的博客-CSDN博客_initial-scale