我们使用js lib retina.js将低质量图像与“视网膜”图像交换(大小乘以2)。问题是,retina.js会为每个无法找到的“视网膜”图像抛出404。
我们拥有一个网站,用户可以上传他们自己的图片,这些图片很可能不是视网膜分辨率。
有没有办法阻止js投掷404?
如果你不知道lib。这是抛出404的代码:
http = new XMLHttpRequest;
http.open('HEAD', this.at_2x_path);
http.onreadystatechange = function() {
if (http.readyState != 4) {
return callback(false);
}
if (http.status >= 200 && http.status <= 399) {
if (config.check_mime_type) {
var type = http.getResponseHeader('Content-Type');
if (type == null || !type.match(/^image/i)) {
return callback(false);
}
}
RetinaImagePath.confirmed_paths.push(that.at_2x_path);
return callback(true);
} else {
return callback(false);
}
}
http.send();
答案 0 :(得分:28)
我看到了一些选项,以缓解这种情况。
对于设置为换出'1x'版本的任何给定'2x'图像,retina.js首先通过XMLHttpRequest
请求验证图像的可用性。成功响应的路径缓存在一个数组中,然后下载图像。
以下更改可能会提高效率:
可以缓存失败的XMLHttpRequest
验证尝试:目前,只有先前成功的'2x'路径验证尝试才会被跳过。因此,失败的尝试可能会再次发生。在实践中,这与初始加载页面时验证过程发生的情况无关。但是,如果结果持续存在,跟踪失败将防止重复出现404错误。
在localStorage
中保留'2x'路径验证结果:在初始化期间,retina.js可以检查localStorage
是否有结果缓存。如果找到一个,则可以绕过已经遇到的“2x”图像的验证过程,并且可以下载或跳过“2x”图像。可以验证新遇到的“2x”图像路径,并将结果添加到缓存中。从理论上讲,虽然localStorage
可用,但每个浏览器的图像只会出现一次404。这将适用于域中任何页面的页面。
这是一个快速的检查。可能需要添加过期功能。
https://gist.github.com/4343101/revisions
我必须注意,我对“服务器端”问题的掌握至多是 spotty 。请参加此FWIW
另一个选项是服务器使用重定向代码响应具有@2x
个字符且不存在的图像请求。请参阅this related answer。
特别是:
如果您重定向图像并且它们是可缓存的,那么理想情况下,您可以在遥远的未来为日期设置HTTP Expires标头(以及相应的Cache-Control标头),因此至少在后续访问页面的用户中获胜不得不重新进行重定向。
使用重定向响应将消除404并导致浏览器跳过后续尝试访问不存在的“2x”图像路径。
可以修改视网膜以排除某些图像。
与此相关的提款请求:https://github.com/imulus/retinajs/commit/e7930be
根据拉取请求,不是按标签名称查找<img>
元素,而是可以使用CSS选择器,这可以是retina.js的可配置选项之一。可以创建一个CSS选择器,用于过滤掉用户上传的图像(以及预期不存在'2x'变体的其他图像)。
另一种可能性是为可配置选项添加过滤功能。可以在每个匹配的<img>
元素上调用该函数; return true
会导致下载“2x”变体,而其他任何内容都会导致<img>
被跳过。
基本的默认配置会从the current version更改为:
var config = {
check_mime_type: true,
retinaImgTagSelector: 'img',
retinaImgFilterFunc: undefined
};
Retina.init()
函数会从the current version变为类似:
Retina.init = function(context) {
if (context == null) context = root;
var existing_onload = context.onload || new Function;
context.onload = function() {
// uses new query selector
var images = document.querySelectorAll(config.retinaImgTagSelector),
retinaImages = [], i, image, filter;
// if there is a filter, check each image
if (typeof config.retinaImgFilterFunc === 'function') {
filter = config.retinaImgFilterFunc;
for (i = 0; i < images.length; i++) {
image = images[i];
if (filter(image)) {
retinaImages.push(new RetinaImage(image));
}
}
} else {
for (i = 0; i < images.length; i++) {
image = images[i];
retinaImages.push(new RetinaImage(image));
}
}
existing_onload();
}
};
要在window.onload
开火之前付诸实践,请致电:
window.Retina.configure({
// use a class 'no-retina' to prevent retinajs
// from checking for a retina version
retinaImgTagSelector : 'img:not(.no-retina)',
// or, assuming there is a data-owner attribute
// which indicates the user that uploaded the image:
// retinaImgTagSelector : 'img:not([data-owner])',
// or set a filter function that will exclude images that have
// the current user's id in their path, (assuming there is a
// variable userId in the global scope)
retinaImgFilterFunc: function(img) {
return img.src.indexOf(window.userId) < 0;
}
});
更新:清理并重组。添加了localStorage
增强功能。
答案 1 :(得分:8)
简短回答:仅使用客户端JavaScript无法实现
在浏览代码和一些研究之后,在我看来,retina.js并非真的抛出404错误。
retina.js实际上在做什么是请求文件,只是根据错误代码检查它是否存在。这实际上意味着要求浏览器检查文件是否存在。浏览器是什么给你404,并且没有跨浏览器方式来阻止(我说“跨浏览器”,因为我只检查webkit)。
但是,如果这确实是一个问题,你可以做的是在服务器端做一些事情来防止404s。
基本上,这可能是/retina.php?image= YOUR_URLENCODED_IMAGE_PATH 一个请求,当视网膜图像存在时,该请求可以返回...
{"isRetina": true, "path": "YOUR_RETINA_IMAGE_PATH"}}
如果它没有...
{"isRetina": false, "path": "YOUR_REGULAR_IMAGE_PATH"}}
然后,您可以使用JavaScript调用此脚本并根据需要解析响应。 我并不是说这是唯一或最好的解决方案,只是一种可行的解决方案。
答案 2 :(得分:7)
Retina JS支持图像标签上的属性data-no-retina。 这样它就不会试图找到视网膜图像。
对寻求简单解决方案的其他人有帮助。
<img src="/path/to/image" data-no-retina />
答案 3 :(得分:3)
我更喜欢控制哪些图像被替换。
对于我为@ 2x创建的所有图像,我将原始图像名称更改为包含@ 1x。 (*见下面的注释。)我稍微改变了retina.js,所以它只看[name] @ 1x。[ext] images。
我替换了retina-1.1.0.js中的以下行:
retinaImages.push(new RetinaImage(image));
使用以下行:
if(image.src.match(/@1x\.\w{3}$/)) {
image.src = image.src.replace(/@1x(\.\w{3})$/,"$1");
retinaImages.push(new RetinaImage(image));
}
这使得retina.js只用@ 2x命名图像替换@ 1x命名图像。
(*注意:在探索这个时,似乎Safari和Chrome会自动用@ 2x图像替换@ 1x图像,即使没有安装retina.js。我也懒得跟踪它,但我想它是最新的webkit浏览器的一个功能。事实上,对于跨浏览器支持,retina.js及其上述更改是必要的。)
答案 4 :(得分:2)
其中一个解决方案是使用PHP:
用第一篇文章替换代码:
http = new XMLHttpRequest;
http.open('HEAD', "/image.php?p="+this.at_2x_path);
http.onreadystatechange = function() {
if (http.readyState != 4) {
return callback(false);
}
if (http.status >= 200 && http.status <= 399) {
if (config.check_mime_type) {
var type = http.getResponseHeader('Content-Type');
if (type == null || !type.match(/^image/i)) {
return callback(false);
}
}
RetinaImagePath.confirmed_paths.push(that.at_2x_path);
return callback(true);
} else {
return callback(false);
}
}
http.send();
并在您的站点根目录中添加名为“image.php”的文件:
<?php
if(file_exists($_GET['p'])){
$ext = explode('.', $_GET['p']);
$ext = end($ext);
if($ext=="jpg") $ext="jpeg";
header("Content-Type: image/".$ext);
echo file_get_contents($_GET['p']);
}
?>
答案 5 :(得分:1)
retina.js是静态网页上固定图像的一个很好的工具,但是如果你要检索用户上传的图像,那么正确的工具就是服务器端。我在这里想象PHP,但是同样的逻辑可以应用于任何服务器端语言。
如果上传图片的安全习惯不允许用户通过直接网址访问它们:如果用户成功将恶意脚本上传到您的服务器,则他无法通过网址启动它(www.yoursite.com/uploaded/mymaliciousscript.php
)。因此,如果可以的话......通过某些脚本<img src="get_image.php?id=123456" />
检索上传的图像通常是一个好习惯...(甚至更好,将上传文件夹保留在文档根目录之外)
现在get_image.php脚本可以根据某些条件获得适当的图像123456.jpg或123456@2x.jpg。
http://retina-images.complexcompulsions.com/#setupserver的方法似乎适合您的情况。
首先,通过JS或CSS加载文件,在标题中设置一个cookie:
内部HEAD:
<script>(function(w){var dpr=((w.devicePixelRatio===undefined)?1:w.devicePixelRatio);if(!!w.navigator.standalone){var r=new XMLHttpRequest();r.open('GET','/retinaimages.php?devicePixelRatio='+dpr,false);r.send()}else{document.cookie='devicePixelRatio='+dpr+'; path=/'}})(window)</script>
在BODY开始时:
<noscript><style id="devicePixelRatio" media="only screen and (-moz-min-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2)">#devicePixelRatio{background-image:url("/retinaimages.php?devicePixelRatio=2")}</style></noscript>
现在,每次调用上传图像的脚本时,都会设置一个cookie集,询问视网膜图像(或不是)。
当然,您可以使用提供的retinaimages.php脚本输出图像,但您也可以修改它以满足您的需求,具体取决于您如何从数据库生成和检索图像或从用户隐藏上载目录。
因此,它不仅可以加载适当的图像,而且如果安装了GD2,并且您将原始上载的图像保留在服务器上,它甚至可以调整它的大小并相应地裁剪并将2个缓存的图像大小保存在服务器上。在retinaimages.php源代码中,您可以看到(并复制)它的工作原理:
<?php
$source_file = ...
$retina_file = ....
if (isset($_COOKIE['devicePixelRatio'])) {
$cookie_value = intval($_COOKIE['devicePixelRatio']);
}
if ($cookie_value !== false && $cookie_value > 1) {
// Check if retina image exists
if (file_exists($retina_file)) {
$source_file = $retina_file;
}
}
....
header('Content-Length: '.filesize($source_file), true);
readfile($source_file); // or read from db, or create right size.. etc..
?>
优点:图片只加载一次(3G上的视网膜用户至少不会加载1x + 2x图像),如果启用了cookie,甚至可以无JS ,可以轻松打开和关闭,无需使用Apple命名约定。您加载图像12345并获得设备的正确DPI。
使用url重写,您甚至可以通过将/get_image/1234.jpg重定向到/get_image.php?id=1234.jpg
将其渲染为完全透明答案 6 :(得分:0)
我的建议是你认识到404错误是真正的错误,并按照你应该的方式修复它们,即提供Retina图形。您使您的脚本与Retina兼容,但您没有通过使您的图形工作流与Retina兼容来完成该循环。因此,Retina图形实际上是丢失的。无论图形工作流程开始时是什么,工作流程的输出必须是2个图像文件,低分辨率和Retina 2x。
如果用户上传的照片是3000x2400,您应该将其视为照片的Retina版本,将其标记为2x,然后使用服务器端脚本生成较小的1500x1200非Retina版本,而不是2倍。这两个文件一起构成一个1500x1200 Retina兼容图像,无论显示器是否为Retina,都可以在1500x1200的Web上下文中显示。您不必关心,因为您有与Retina兼容的图像和Retina兼容的网站。 RetinaJS脚本是唯一一个必须关心客户端是否使用Retina的脚本。因此,如果您要从用户那里收集照片,除非您生成2个低分辨率和高分辨率的文件,否则您的任务就不完整。
典型的智能手机拍摄的照片尺寸是智能手机显示屏的10倍以上。所以你应该总是有足够的像素。但是如果你得到的是非常小的图像,比如500px,那么你可以在你的服务器端图像缩小脚本中设置一个断点,以便在下面,上传的照片用于低分辨率版本,脚本制作2x拷贝这将不会比非Retina图像好,但它将与Retina兼容。
使用这个解决方案,你的整个问题“是2x图像在哪里?”消失了,因为它总是在那里。与Retina兼容的网站将很乐意使用您的Retina兼容照片数据库,无需任何投诉。