将视口设置为"缩放以适应宽度和高度"



我正在开发一个适合特定宽度和高度的网站(885x610div,边框为1px,上边距为3px)。我希望用户永远不必为了查看整个div而滚动或缩放;它应该始终是完全可见的。由于设备具有各种各样的分辨率和纵横比,所以想到的想法是设置";视口";使用JavaScript动态地标记meta。这样,div将始终是相同的尺寸,不同的设备将不得不以不同的方式缩放,以便在其视口中适应整个div。我尝试了一下我的想法,得到了一些奇怪的结果。

以下代码适用于第一页加载(在Android 4.4.0上的Chrome 32.0.1700.99中测试),但当我刷新时,缩放级别会发生变化。此外,如果我注释掉alert,它甚至在第一页加载时也不起作用。

Fiddle

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
<script type="text/javascript">
function getViewportWidth() {
if (window.innerWidth) {
return window.innerWidth;
}
else if (document.body && document.body.offsetWidth) {
return document.body.offsetWidth;
}
else {
return 0;
}
}
function getViewportHeight() {
if (window.innerHeight) {
return window.innerHeight;
}
else if (document.body && document.body.offsetHeight) {
return document.body.offsetHeight;
}
else {
return 0;
}
}
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
var actual_width = getViewportWidth();
var actual_height = getViewportHeight();
var min_width = 887;
var min_height = 615;
var ratio = Math.min(actual_width / min_width, actual_height / min_height);
if (ratio < 1) {
document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);
}
}
alert(document.querySelector('meta[name="viewport"]').getAttribute('content'));
</script>
<title>Test</title>
<style>
body {
margin: 0;
}
div {
margin: 3px auto 0;
width: 885px;
height: 610px;
border: 1px solid #f00;
background-color: #fdd;
}
</style>
</head>
<body>
<div>
This div is 885x610 (ratio is in between 4:3 and 16:10) with a 1px border and 3px top margin, making a total of 887x615.
</div>
</body>
</html>

我该怎么做才能使这个网站的比例既适合宽度又适合高度?

可以获得一致的行为。但不幸的是,它非常复杂。我正在编写一个脚本,该脚本可以检测伪造的代理,并相应地将视口动态地重新缩放到桌面或其他伪造的代理。我还面临着安卓/Chrome以及iOS模拟器的缩放问题。。。

要绕过它,需要禁用缩放和/或设置视口两次。在第一次通过时,最好像现在这样在<head>中内联,您设置您的缩放比例并暂时禁用用户缩放以防止缩放问题,对所有3个缩放比例使用相同的固定值,如:

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale='+scale+',maximum-scale='+scale+',initial-scale='+scale);

然后,为了恢复缩放,您在DOMContentLoaded上再次设置视口,使用相同的比例,只是这次您设置了正常的最小/最大比例值以恢复用户缩放:

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10');

在您的上下文中,因为布局是固定的并且比视口大,所以可能需要initial-scale='+scale来为DOMContentLoaded:提供更合理的替代方案

document.querySelector('meta[name=viewport]').setAttribute('content', 'width='+width+',minimum-scale=0,maximum-scale=10,initial-scale='+scale);

这应该可以让视口在Webkit浏览器中按照您的意愿重新缩放,而不会出现缩放问题。我只在webkit中说,因为遗憾的是,IE和Firefox不支持根据以下浏览器兼容性表更改视口:http://www.quirksmode.org/mobile/tableViewport.html

IE有自己的方法来动态更改视口,这实际上是IE捕捉模式做出响应所需要的。http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/

因此,对于IEMobile和IE SnapMode(IE10&11),您需要在<head>中动态插入一个内联<style>

<script>
var s = document.createElement('style');
s.appendChild(document.createTextNode('@-ms-viewport{width:'+width+'px')+';}'));
document.head.appendChild(s);
</script>

不幸的是,Firefox两者都没有:正如上面的兼容性表所示,视口是一次性设置的。目前,由于缺乏其他方法,使用CSS Transform(正如@imcg所指出的)是在Android或Gecko OS上更改FireFox Mobile视口的唯一方法。我刚刚测试了它,它在固定尺寸设计的背景下工作。(在"响应式设计上下文"中,可以通过CSS转换将布局重新缩放得更大,比如在手机上的桌面大小,但Firefox仍然读取手机大小的MQ。因此,在RWD上下文中需要注意这一点。/除了webkit之外)

不过,我注意到在安卓系统上CSSTransform会导致一些随机的Webkit崩溃,所以我建议Safari/Chrome/Opera的视口方法更可靠。

此外,为了获得视口宽度的跨浏览器可靠性,您还必须面对/修复浏览器之间innerWidth的总体不一致性(请注意,documentElement.clientWidth更可靠地获得准确的布局像素宽度),并且您还必须处理curksmode.org表中所示的devicePixelRatio差异。

更新:事实上,在对我自己的Firefox问题的解决方案进行了更多思考之后,我刚刚找到了一种在Firefox Mobile中使用document.write()覆盖视口的方法,只应用了一次:

document.write('<meta name="viewport" content="width='+width+'px,initial-scale='+scale+'">');

刚刚在Webkit和Firefox中成功测试了这一点,没有缩放问题。我不能在Window Phone上测试,所以我不确定document.write是否适用于IEMobile。。。

我知道这已经晚了两年,但我花了很多时间来解决这个问题,我想分享我的解决方案。我发现原来的问题很有帮助,所以我觉得发布这个答案是我回馈的方式。我的解决方案适用于iPhone 6和7英寸Galaxy Tab。我不知道它在其他设备上的表现如何,但我猜它应该基本正常。

我将视口逻辑分离到一个外部文件中,以便重新使用首先,以下是如何使用代码:

<HTML>
<HEAD>    
<SCRIPT type="text/javascript" src="AutoViewport.js"></SCRIPT>
</HEAD>
<BODY>
<SCRIPT>
AutoViewport.setDimensions(yourNeededWidth, yourNeededHeight);
</SCRIPT>
<!-- The rest of your HTML goes here -->
</BODY>
</HTML>

事实上,我把我想要的宽度和高度稍微增加了一点(15像素左右),这样我想要的显示器就可以很好地框起来了。此外,请注意使用代码,您不必在HTML中指定视口标记。如果我的库不存在,它将自动为您创建一个。这是AutoViewport.js:

/** Steven Yang, July 2016
Based on http://stackoverflow.com/questions/21419404/setting-the-viewport-to-scale-to-fit-both-width-and-height , this Javascript code allows you to 
cause the viewport to auto-adjust based on a desired pixel width and height
that must be visible on the screen.
This code has been tested on an iPhone6 and a 7" Samsung Galaxy Tab.
In my case, I have a game with the exact dimensions of 990 x 660.  This
script allows me to make the game render within the screen, regardless 
of whether you are in landscape or portrait mode, and it works even
when you hit refresh or rotate your device.
Please use this code freely.  Credit is appreciated, but not required!
*/
function AutoViewport() {}
AutoViewport.setDimensions = function(requiredWidth, requiredHeight) {
/* Conditionally adds a default viewport tag if it does not already exist. */
var insertViewport = function () {
// do not create if viewport tag already exists
if (document.querySelector('meta[name="viewport"]'))
return;
var viewPortTag=document.createElement('meta');
viewPortTag.id="viewport";
viewPortTag.name = "viewport";
viewPortTag.content = "width=max-device-width, height=max-device-height,initial-scale=1.0";
document.getElementsByTagName('head')[0].appendChild(viewPortTag);
};
var isPortraitOrientation = function() {
switch(window.orientation) {  
case -90:
case 90:
return false;
}
return true;
};
var getDisplayWidth = function() {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
if (isPortraitOrientation())
return screen.width;
else
return screen.height;
}
return screen.width;
}
var getDisplayHeight = function() {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
if (isPortraitOrientation())
return screen.height;
else
return screen.width;
}
// I subtract 180 here to compensate for the address bar.  This is imperfect, but seems to work for my Android tablet using Chrome.
return screen.height - 180;
}
var adjustViewport = function(requiredWidth, requiredHeight) {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)){
var actual_height = getDisplayHeight();
var actual_width = getDisplayWidth();
var min_width = requiredWidth;
var min_height = requiredHeight;
var ratio = Math.min(actual_width / min_width, actual_height / min_height);
document.querySelector('meta[name="viewport"]').setAttribute('content', 'initial-scale=' + ratio + ', maximum-scale=' + ratio + ', minimum-scale=' + ratio + ', user-scalable=yes, width=' + actual_width);
}    
};
insertViewport();
adjustViewport(requiredWidth, requiredHeight);
window.addEventListener('orientationchange', function() {
adjustViewport(requiredWidth, requiredHeight);      
});
};

如果您将我的代码与问题中的原始代码进行仔细比较,您会注意到一些差异。例如,我从不依赖视口的宽度或高度。相反,我依赖于屏幕对象。这一点很重要,因为当您刷新页面或旋转屏幕时,视口的宽度和高度可以更改,但screen.width和screen.height永远不会更改。接下来你会注意到的是,我不检查(ratio<1)。当刷新或旋转屏幕时,该检查会导致不一致,所以我删除了它。此外,我还包含了一个用于屏幕旋转的处理程序。

最后,我只想对提出这个问题的人说声谢谢,因为他为我打下了基础,节省了我的时间!

如果您无法通过更改视口元标记在设备之间获得一致的行为,则可以使用CSS3转换在不更改维度的情况下进行缩放:

if (ratio < 1) {
var box = document.getElementsByTagName('div')[0];
box.style.webkitTransform = 'scale('+ratio+')';
box.style.webkitTransformOrigin = '0 0';
}
console.log(box.offsetWidth); // always original width
console.log(box.getBoundingClientRect().width); // new width with scaling applied

注意,我在这里省略了除了webkit之外的任何供应商前缀,以保持简单。

要使缩放的div居中,可以使用translate-transforms:

var x = (actual_width - min_width * ratio) / 2;
var y = (actual_height - min_height * ratio) / 2;
box.style.webkitTransform = 'translateX('+x+'px) translateY('+y+'px) scale('+ratio+')';
box.style.webkitTransformOrigin = '0 0';

将您的视口替换为:

<META NAME="viewport" CONTENT="width=device-width, height=device-height, initial-scale=1, user-scalable=no"/>

这里的用户scalable=0将为您完成这项工作。

这对你有用。如果它仍然不起作用,我们将不得不扩展视口,因此替换您的视口并添加以下内容:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, target-densitydpi=medium-dpi, user-scalable=0" />

对于您的javascript错误,请查看以下链接:

使用视口元标签缩放适合移动web内容

将div的父元素(htmlbody)的高度和宽度设置为100%,并将边距清零。

html, body {
margin: 0;
height: 100%;
width: 100%;
}

接下来,因为您希望在div上有一个边框,所以在指定元素的宽度/高度时,需要确保包含边框宽度,为此,请在div上使用box-sizing: border-box

因为您想要在div上有一个3px的上边距,所以div的相对位置会导致高度过高3px。要解决此问题,请在div上使用绝对定位,并将top、left和bottom设置为0。

div {
position: absolute;
top: 3px;
left: 0;
bottom: 0;
box-sizing: border-box;
width: 100%;
border: 1px solid #f00;
background-color: #fdd;
}

下面是一个工作示例。

更新:我想我误解了这个问题。下面是一个示例代码,它根据设备的视口调整"缩放">

http://jpanagsagan.com/viewport/index2.html

请注意,我使用jquery来附加元标记,因为我在使用vanilla附加时遇到了问题。

我注意到的一件事是,如果你在HTML中硬编码并通过JS进行更改,文档将不会应用更正(需要验证)。如果HTML中没有以前的标记,我可以通过JS进行更改,因此我使用了append。

你可以使用这个比例,但在这个例子中,我使用了视口的宽度除以div的宽度

希望这能有所帮助。

更新:我想我误解了这个问题。下面是一个示例代码,它根据设备的视口调整"缩放">

http://jpanagsagan.com/viewport/index2.html

请注意,我使用jquery来附加元标记,因为我在使用vanilla附加时遇到了问题。

我注意到的一件事是,如果你在HTML中硬编码并通过JS进行更改,文档将不会应用更正(需要验证)。如果HTML中没有以前的标记,我可以通过JS进行更改,因此我使用了append。

你可以使用这个比例,但在这个例子中,我使用了视口的宽度除以div的宽度

希望这能有所帮助。

我认为史蒂文的答案应该是公认的。

如果它对其他人有帮助,我会在Steven的AutoViewport.js中添加以下两件事,以便在用户处于横向视图时将视图集中在视口中:

添加"var viewport_margin=0;"作为代码的第一行(在"function AutoViewport(){}"之前添加它自己的行)。

在"document.querySelector('meta[name="viewport"]').setAttribute('content','initial scale='+ratio+',maximum scale='+Rratio+',minimum scale='%+Rratio+',user scalable=yes,width='+actual_width);">

感谢所有发帖将此解决方案公之于众的人。

更新:在Android中,我的添加似乎只适用于API 24+。不确定他们为什么不使用API 19-23。有什么想法吗?

最新更新