在retina.js库中抑制404s

时间:2012-12-04 13:42:22

标签: javascript http-status-code-404 retina.js

我们使用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();

7 个答案:

答案 0 :(得分:28)

我看到了一些选项,以缓解这种情况。

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

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

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

  • 可以缓存失败的XMLHttpRequest验证尝试:目前,只有先前成功的'2x'路径验证尝试才会被跳过。因此,失败的尝试可能会再次发生。在实践中,这与初始加载页面时验证过程发生的情况无关。但是,如果结果持续存在,跟踪失败将防止重复出现404错误。

  • localStorage中保留'2x'路径验证结果:在初始化期间,retina.js可以检查localStorage是否有结果缓存。如果找到一个,则可以绕过已经遇到的“2x”图像的验证过程,并且可以下载或跳过“2x”图像。可以验证新遇到的“2x”图像路径,并将结果添加到缓存中。从理论上讲,虽然localStorage可用,但每个浏览器的图像只会出现一次404。这将适用于域中任何页面的页面。

这是一个快速的检查。可能需要添加过期功能。

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

使用HTTP重定向标头

我必须注意,我对“服务器端”问题的掌握至多是 spotty 。请参加此FWIW

另一个选项是服务器使用重定向代码响应具有@2x个字符且不存在的图像请求。请参阅this related answer

特别是:

  

如果您重定向图像并且它们是可缓存的,那么理想情况下,您可以在遥远的未来为日期设置HTTP Expires标头(以及相应的Cache-Control标头),因此至少在后续访问页面的用户中获胜不得不重新进行重定向。

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

retina.js可以更具选择性

可以修改视网膜以排除某些图像。

与此相关的提款请求: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兼容照片数据库,无需任何投诉。