繁体   English   中英

在retina.js库中抑制404s

[英]Suppressing 404s in retina.js library

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

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

有没有办法阻止js投掷404s?

如果你不知道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();

我看到了一些选项来减轻这种情况。

增强并保持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重定向标头

我必须指出,我对“服务器端”问题的把握充其量只是参差不齐 请带上这个FWIW

另一个选项是服务器使用重定向代码响应具有@2x字符且不存在的图像请求。 看到这个相关的答案

特别是:

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

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

retina.js可以更具选择性

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

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

根据pull请求,不是按标签名称查找<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增强功能。

简短回答:仅使用客户端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调用此脚本并根据需要解析响应。 我并不是说这是唯一或最好的解决方案,只是一个可以工作的解决方案。

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

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

<img src="/path/to/image" data-no-retina />

我更喜欢控制哪些图像被替换。

对于我创建了@ 2x for的所有图像,我将原始图像名称更改为包含@ 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会自动用@ 2x图像替换@ 1x图像,即使没有安装retina.js。我也懒得跟踪它,但我想它是一个功能最新的webkit浏览器。实际上,retina.js及其上述更改对于跨浏览器支持是必要的。)

解决方案之一是使用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']);
 }
?>

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上的视网膜用户至少不会加载1x + 2x图像),如果启用了cookie,即使没有JS ,也可以轻松打开和关闭,无需使用苹果命名约定。 您加载图像12345,您获得了正确的设备DPI。

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

我的建议是你认识到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兼容照片数据库,无任何投诉。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM