25

我们使用 js libretina.js将低质量图像与“视网膜”图像(大小乘以 2)交换。问题是,retina.js 会为每个找不到的“视网膜”图像抛出 404。

我们拥有一个网站,用户可以在其中上传他们自己的图片,这些图片很可能不是视网膜分辨率。

有没有办法防止js抛出404s?

如果你不知道库。这是抛出 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();
4

7 回答 7

28

我看到有几个选项可以缓解这种情况。

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

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

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

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

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

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

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

使用 HTTP 重定向标头

我必须指出,我对“服务器端”问题的理解充其量是参差不齐的。请收下这个 FWIW

另一种选择是让服务器使用重定向代码响应具有@2x字符但不存在的图像请求。请参阅此相关答案

尤其:

如果您重定向图像并且它们是可缓存的,那么理想情况下,您会为遥远的将来的某个日期设置一个 HTTP Expires 标头(以及相应的 Cache-Control 标头),因此至少在用户后续访问该页面时不会有再次通过重定向。

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

可以使retina.js 更具选择性

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

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

根据拉取请求,<img>可以使用 CSS 选择器,而不是按标签名称查找元素,这可以是 Retina.js 的可配置选项之一。可以创建一个 CSS 选择器来过滤掉用户上传的图像(以及其他预期不存在“2x”变体的图像)。

另一种可能性是将过滤器功能添加到可配置选项中。可以在每个匹配的<img>元素上调用该函数;areturn 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增强功能。

于 2012-12-15T11:06:17.767 回答
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 调用此脚本并根据需要解析响应。我并不是说这是唯一或最好的解决方案,只是一个可行的解决方案。

于 2012-12-14T17:26:57.830 回答
7

Retina JS 支持图像标签上的属性 data-no-retina。这样它就不会试图找到视网膜图像。

对于寻求简单解决方案的其他人很有帮助。

<img src="/path/to/image" data-no-retina />
于 2015-05-05T01:36:23.373 回答
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 会自动将 @1x 图像替换为 @2x 图像,即使没有安装retina.js。我懒得追踪这个,但我想这是一个功能最新的 webkit 浏览器。实际上,retina.js 和对它的上述更改对于跨浏览器支持是必要的。)

于 2013-11-25T20:57:24.643 回答
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']);
 }
?>
于 2012-12-19T10:07:44.167 回答
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:

头部内部:

<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>

在身体的开头:

<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也可以工作,可以轻松打开和关闭,无需使用苹果命名约定。您加载图像 12345 并为您的设备获得正确的 DPI。

通过 url 重写,您甚至可以通过将 /get_image/1234.jpg 重定向到 /get_image.php?id=1234.jpg 来使其完全透明

于 2012-12-18T15:24:26.313 回答
0

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

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

典型的智能手机拍摄的照片大小超过智能手机显示屏的 10 倍。所以你应该总是有足够的像素。但是如果你得到非常小的图像,比如 500px,那么你可以在你的服务器端图像减少脚本中设置一个断点,这样在下面,上传的照片将用于低分辨率版本,脚本会生成 2x 副本这不会比非 Retina 图像更好,但它将与 Retina 兼容。

使用此解决方案,您的整个问题是“是否存在 2x 图像?” 消失,因为它一直都在。兼容 Retina 的网站将愉快地使用您的 Retina 兼容照片数据库,没有任何投诉。

于 2015-10-03T18:49:31.800 回答