抑制 retina.js 库中的 404 错误页面

25
我们使用js库retina.js,它会将低质量图片替换为"retina" 图片(尺寸扩大2倍)。问题是,如果找不到“retina”图像,retina.js就会抛出404错误。
我们拥有一个网站,用户可以上传自己的图片,这些图片很可能没有retina分辨率。
有没有办法防止JS抛出404?
如果您不了解该库。 这里是代码引发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();

1
如果找不到图像,您希望发生什么?如果您只想返回低分辨率图像,则只需将最后两个return callback(false)更改为return callback(true)即可。 - Lars Kotthoff
从技术上讲,这也回答了我的问题,我正在寻找一种方法,使这个js插件只搜索带有数据属性的图像并替换它们的源代码,通过从retina.js插件中删除上面的代码,问题得到解决。在我的情况下,我需要这样做。 - Hooman Askari
7个回答

28

有几个选项可以缓解这个问题。

增强并持久化retina.js的HTTP调用结果缓存

对于任何设置为交换'1x'版本的'2x'图像,retina.js首先通过XMLHttpRequest请求验证图像的可用性。成功响应的路径会被缓存在数组中,并下载该图像。

以下更改可能会提高效率:

  • 可以缓存失败的XMLHttpRequest验证尝试:目前,仅在以前成功时才跳过'2x'路径验证尝试。因此,失败的尝试可能会再次发生。实际上,这并不重要,因为验证过程发生在页面最初加载时。但是,如果结果持久化,跟踪失败将防止404错误的重复出现。

  • localStorage中持久化'2x'路径验证结果:在初始化期间,retina.js可以检查localStorage是否存在结果缓存。如果找到一个,则可以绕过已经遇到的'2x'图像的验证过程,并下载或跳过'2x'图像。新遇到的'2x'图像路径可以进行验证,并将结果添加到缓存中。理论上,只要localStorage可用,每个浏览器上的每个图像只会发生一次404错误。这将适用于域上的任何页面。

以下是快速的workup。可能需要添加过期功能。

https://gist.github.com/4343101/revisions

使用HTTP重定向头

我必须指出,我对“服务器端”事务的理解非常有限,请谨慎参考此内容

另一个选择是服务器响应带有@2x字符且不存在的图像请求的重定向代码。请参见相关回答

特别地:

如果您重定向图像并且它们是可缓存的,则应该为遥远的未来设置HTTP Expires头(和适当的Cache-Control头),以便在随后访问页面时,用户不必再次通过重定向。

使用重定向响应将消除404,并使浏览器跳过不存在的'2x'图像路径的后续尝试。

retina.js可以更具选择性

可以修改retinajs以排除一些图像。

与此相关的拉取请求:https://github.com/imulus/retinajs/commit/e7930be

根据拉取请求,可以使用CSS选择器而不是标记名称查找<img>元素。 CSS选择器可以成为retina.js的可配置选项之一。可以创建一个CSS选择器来过滤用户上传的图像(和其他期望不存在'2x'变体的图像)。

另一个可能性是向可配置选项添加过滤函数。该函数将在每个匹配的<img>元素上调用;return true将导致下载'2x'变量,否则将跳过<img>

基本的默认配置将从var config = { check_mime_type: true, retinaImgTagSelector: 'img', retinaImgFilterFunc: undefined };

Retina.init() 函数的变化将从当前版本变成如下形式:

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增强功能。


这不仅可以防止404错误,还可以节省整个浪费的HTTP请求。由于问题图像是用户上传的图像,因此似乎很容易区分img标签。 - svachalek
是的,我认为使用过滤函数可以做出一些非常丰富的东西。过滤函数可以意识到服务器上不同路径的相关性,它们被添加到的页面上下文等等。这可能是一个非常有趣的设置... - tiffon

8
简短回答:仅使用客户端JavaScript不可能实现。
在浏览代码并进行一些研究后,我发现retina.js实际上并没有真正引发404错误。
实际上,retina.js正在请求文件,并根据错误代码检查文件是否存在。这实际上意味着它正在“要求浏览器检查文件是否存在”。浏览器会给出404,而且没有跨浏览器的方法可以防止这种情况(我说“跨浏览器”,因为我只检查了Webkit)。
但是,如果这确实是一个问题,您可以在服务器端执行某些操作以完全避免404。
基本上,例如,/ retina.php?image = YOUR_URLENCODED_IMAGE_PATH,当视网膜图像存在时,请求可以返回以下内容...
{"isRetina": true, "path": "YOUR_RETINA_IMAGE_PATH"}}

如果它没有……

{"isRetina": false, "path": "YOUR_REGULAR_IMAGE_PATH"}}

你可以编写一些JavaScript代码调用此脚本并根据需要解析响应。我不是在声称这是唯一或最好的解决方案,只是一个可行的方案。

7
Retina JS支持在图像标签上使用data-no-retina属性。这样它就不会尝试查找视网膜图片。 对于其他寻找简单解决方案的人来说,这非常有帮助。
<img src="/path/to/image" data-no-retina />

完美!正是拥有用户生成的照片库所需的。 - jenjenut233

3

我希望能更好地控制哪些图片被替换。

对于所有我已经为之创建了@2x版本的图片,我将原始图片的名称改为包含@1x。(* 详见下面的注释) 我稍微修改了retina.js,使其仅查看[name]@1x.[ext] 图片。

我替换了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甚至在没有安装retina.js的情况下也会自动将@1x图片替换为@2x图片。我懒得去追踪这个问题,但我想这应该是最新的webkit浏览器的一个功能。正因为如此,retina.js和上述更改对于跨浏览器支持是必要的。)

我知道这是一篇旧帖子,但我尝试了这种技术,结果导致网站出现了故障。有时候图片根本无法加载,刷新一下就好了,但再次刷新又会出问题。这太不稳定了。 - Brian Peat

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']);
 }
?>

1

retina.js是一个很好的工具,用于静态网页上的固定图像,但如果您正在检索用户上传的图像,则正确的工具是服务器端。我在这里想象PHP,但相同的逻辑可以应用于任何服务器端语言。

假设上传的图像的一个好的安全习惯是不要让用户通过直接URL访问它们:如果用户成功地将恶意脚本上传到您的服务器,则不应该能够通过URL启动它(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上的Retina用户不会加载1x+2x图像),即使启用了cookie,也可以在没有JS的情况下工作,可以轻松地开关,无需使用苹果命名约定。您加载图像12345,您将获得适合您设备的正确DPI。

通过URL重写,您甚至可以通过将/get_image/1234.jpg重定向到/get_image.php?id=1234.jpg来完全透明地呈现它。


0

我的建议是,您应该将404错误识别为真正的错误,并按照应有的方式修复它们,即提供Retina图形。您已经使您的脚本兼容Retina,但是您没有通过使您的图形工作流程兼容Retina来完成这个过程。因此,Retina图形实际上是缺失的。无论您的图形工作流程的起点是什么,工作流程的输出必须是2个图像文件,一个低分辨率和一个Retina 2x。

如果用户上传的照片是3000x2400,您应该将其视为照片的Retina版本,标记为2x,然后使用服务器端脚本生成一个较小的1500x1200非Retina版本,不带2x。然后,这两个文件组成一个1500x1200 Retina兼容图像,在Web上下文中显示时,无论显示器是否为Retina,都可以显示在1500x1200。您不必关心,因为您拥有一个Retina兼容的图像和Retina兼容的网站。只有RetinaJS脚本需要关心客户端是否使用Retina。因此,如果您正在从用户那里收集照片,则除非您生成2个文件(低分辨率和高分辨率),否则您的任务尚未完成。

典型的智能手机拍摄的照片大小是智能手机显示屏大小的10倍以上。因此,您应该始终拥有足够的像素。但是,如果您得到的是非常小的图像,比如500px,那么您可以在服务器端的图像缩小脚本中设置断点,以便在下面使用上传的照片作为低分辨率版本,并且脚本制作一个2倍的副本,它不会比非Retina图像更好,但它将是Retina兼容的。

通过这种解决方案,“2x图像是否存在”的整个问题都消失了,因为它总是存在的。Retina兼容的网站将愉快地使用您的Retina兼容的照片数据库,而没有任何抱怨。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接