为了更好地方便网页作者在移动浏览器上设置合适自己网页的viewport,Apple在meta标签中引入了viewport属性,相关的介绍可以看这里:《Using the viewport》
http://developer.apple.com/library/ios/#documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html
scale
这里缩放比例指的是设备像素和CSS像素之间的比例,即可以认为是device-width/height和visual viewport(可以通过window.innerWidth/Height获取)之间的比例。
旋转屏幕
Safari旋转屏幕是保持visual viewport的宽度不变,除非超出最大/最小缩放比例,所以旋转屏幕后缩放比例会发生变化。
Viewport的默认值(IOS)
首先来看看width的默认值,前面和Apple的文档中都有提到viewport的默认值中width为980px,但是当initial-scale被设置为1.0的时候,Safari会认为width的值为device-width。所以如你想设置initial-scale为1.0并且viewport的宽度为980px时,你需要同时设置这两个属性,详见《Using the viewport》。
其他属性的默认值文档中虽然没有提到,但是很容易试验出来。
minimum-scale,它的值是根据排版出来的document大小来计算,Safari会取参考值0.25,device-width/document.width和device-height/document.heght的最大值,可以看出Safari的设计是想保证visual viewport刚好包含文档的整个宽度或者刚好包含文档的整个高度。
maxmum-scale,如果没有指定,Safari会取maxmum-scale为5。
initial-scale,如果没有指定,Safari会取initial-scale = minimum-scale。
基于以上这些结论,可以比较容易地知道不同网页在不同meta viewport设置下的layout viewport和初始的visual viewport,我们还是以(三)中的那个页面为例来说明,没有设置viewport属性并且文档高度足够大,如果页面中存在比较大的overflow元素,例如width:2000px,那么文档的宽度document.width就是2000,Safari Mobile根据以上规则计算出的minimum-scale和initial-scale就是document.width/device-width,所以初始打开页面会是这样:
如果没有overflow元素,document.width就是viewport的大小980,初始打开页面就会是这样:
如果 网页通过javascript动态调整页面的宽度或高度,那么就有可能导致Safari Mobile打开页面后计算出的minimum-scale和initial-scale不断变化而发生抖动,在用户进行了一次缩放或者旋屏之后抖动停止。
initial-scale对width的影响
如果没有设置width而设置了initial-scale,那么width = device-width / initial-scale
如果设置了width并且设置了initial-scale,那么width为device-width / initial-scale和设置的width间的最大值
document大小对minimum-scale的影响
即使网页作者指定了minimum-scale的值,它也会根据排版出来的document大小来调整,最终的值为minimum-scale,document.width/device-width和document.heght/device-height三者之间的最大值。
Safari中Viewport的处理流程
viewport相关的数据结构
viewport参数在Document中保存的结构如下所示:
struct ViewportArguments {
enum {
ValueAuto = -1,
ValueDeviceWidth = -2,
ValueDeviceHeight = -3,
ValuePortrait = -4,
ValueLandscape = -5
};
float width;
float minWidth;
float maxWidth;
float height;
float minHeight;
float maxHeight;
float zoom;
float minZoom;
float maxZoom;
float userZoom;
float orientation;
};
结构中每一个值都对应这meta标签中viewport的属性值,默认值都是ValueAuto(-1)。
触发Viewport更新的时机
在WebKit源码中,触发viewport更新的时机只有两处,分别是在加载第一次接收到数据并且Document创建之后和Meta标签处理时解析到viewport属性时,相应的函数调用关系图如下:
在Document创建之后更新viewport时ViewportArguments为默认值ValueAuto。
viewportArguments的处理
线程分工
在对Viewport的处理中,内核线程仅完成viewport的解析步骤就将viewport参数抛给主线程来处理。
主线程会完成viewport的计算和处理工作。除了内核线程抛转来的viewport消息,一些其它情况也会触发主线程中viewport的计算步骤。
在WebKit源码中,WebCore只解析viewport的属性并不处理ViewportArguments(通过Document::processArguments函数解析Viewport参数),而是将其交给ChromeClient来处理,对应的函数调用关系图如下:
在Safari的ChromeClientIOS::dispatchViewportPropertiesDidChange函数中会调用Frame::dictionaryForViewportArguments函数将Viewport参数封装成NSDictionary的形式传递给主线程来处理。
NSDictionary* Frame::dictionaryForViewportArguments(const ViewportArguments& arguments) const
{
return [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:NSFloatValue(arguments.initialScale), NSFloatValue(arguments.minimumScale),
NSFloatValue(arguments.maximumScale), NSFloatValue(arguments.userScalable),
NSFloatValue(arguments.width), NSFloatValue(arguments.height), nil]
forKeys:[NSArray arrayWithObjects:@"initial-scale", @"minimum-scale", @"maximum-scale", @"user-scalable", @"width", @"height", nil]];
}
主线程在收到消息后会调用UIWebDocumentView的viewportConfigurationsDidChange函数来处理viewport参数。
根据前面的讨论,viewport中最小缩放比例会根据document的大小进行调整,而document的大小在动态网页中经常会发生变化,这是viewport就需要重新计算。所以主线程处理Viewport参数的函数viewportConfigurationsDidChange除了被内核线程Document::updateViewportArguments抛转的消息触发外,还有可能在文档大小发生变化时被调用。
另外,观察Safari中Viewport计算函数的调用栈,还有如下一些情况也可能会触发viewport的重新计算。