简体   繁体   English

在retina.js库中抑制404s

[英]Suppressing 404s in retina.js library

We use the js lib retina.js which swaps low quality images with "retina" images (size times 2). 我们使用js lib retina.js ,它将低质量图像与“视网膜”图像交换( 大小乘以2)。 The problem is, that retina.js throws a 404 for every "retina" image which can't be found. 问题是,retina.js为每个无法找到的“视网膜”图像抛出404。

We own a site where users can upload their own pictures which are most likely not in a retina resolution. 我们拥有一个网站,用户可以上传他们自己的图片,这些图片很可能不是视网膜分辨率。

Is there no way to prevent the js from throwing 404s? 有没有办法阻止js投掷404s?

If you don't know the lib. 如果你不知道lib。 Here is the code throwing the 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();

There are a few options that I see, to mitigate this. 我看到了一些选项来减轻这种情况。

Enhance and persist retina.js' HTTP call results caching 增强并保持retina.js的HTTP调用结果缓存

For any given '2x' image that is set to swap out a '1x' version, retina.js first verifies the availability of the image via an XMLHttpRequest request. 对于设置为换出“1x”版本的任何给定“2x”图像,retina.js首先通过XMLHttpRequest请求验证图像的可用性。 Paths with successful responses are cached in an array and the image is downloaded. 成功响应的路径缓存在数组中,并下载映像。

The following changes may improve efficiency: 以下更改可能会提高效率:

  • Failed XMLHttpRequest verification attempts can be cached: Presently, a '2x' path verification attempt is skipped only if it has previously succeeded. 可以缓存失败的XMLHttpRequest验证尝试:目前,只有先前成功的'2x'路径验证尝试才会被跳过。 Therefore, failed attempts can recur. 因此,失败的尝试可能会再次发生。 In practice, this doesn't matter much beacuse the verification process happens when the page is initially loaded. 在实践中,这与初始加载页面时验证过程发生的情况无关。 But, if the results are persisted, keeping track of failures will prevent recurring 404 errors. 但是,如果结果持续存在,跟踪故障将防止重复出现404错误。

  • Persist '2x' path verification results in localStorage : During initialization, retina.js can check localStorage for a results cache. localStorage持续'2x'路径验证结果:在初始化期间,retina.js可以检查localStorage以获得结果缓存。 If one is found, the verification process for '2x' images that have already been encountered can be bypassed and the '2x' image can either be downloaded or skipped. 如果找到一个,则可以绕过已经遇到的“2x”图像的验证过程,并且可以下载或跳过“2x”图像。 Newly encounterd '2x' image paths can be verified and the results added to the cache. 可以验证新遇到的“2x”图像路径,并将结果添加到缓存中。 Theoretically, while localStorage is available, a 404 will occur only once for an image on a per-browser basis. 从理论上讲,虽然localStorage可用,但每个浏览器的图像只会出现一次404。 This would apply across pages for any page on the domain. 这将适用于域上任何页面的页面。

Here is a quick workup. 这是一个快速的检查。 Expiration functionality would probably need to be added. 可能需要添加到期功能。

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

Employ an HTTP redirect header 使用HTTP重定向标头

I must note that my grasp of "server-side" matters is spotty , at best. 我必须指出,我对“服务器端”问题的把握充其量只是参差不齐 Please take this FWIW 请带上这个FWIW

Another option is for the server to respond with a redirect code for image requests that have the @2x characters and do not exist. 另一个选项是服务器使用重定向代码响应具有@2x字符且不存在的图像请求。 See this related answer . 看到这个相关的答案

In particular: 特别是:

If you redirect images and they're cacheable, you'd ideally set an HTTP Expires header (and the appropriate Cache-Control header) for a date in the distant future, so at least on subsequent visits to the page users won't have to go through the redirect again. 如果您重定向图像并且它们是可缓存的,那么理想情况下,您可以在遥远的未来为日期设置HTTP Expires标头(以及相应的Cache-Control标头),因此至少在后续访问页面时用户不会再次进行重定向。

Employing the redirect response would get rid of the 404s and cause the browser to skip subsequent attempts to access '2x' image paths that do not exist. 使用重定向响应将消除404并导致浏览器跳过后续尝试访问不存在的“2x”图像路径。

retina.js can be made more selective retina.js可以更具选择性

retinajs can be modified to exclude some images from consideration. 可以修改视网膜以排除一些图像。

A pull request related to this: https://github.com/imulus/retinajs/commit/e7930be 与此相关的拉取请求: https//github.com/imulus/retinajs/commit/e7930be

Per the pull request, instead of finding <img> elements by tag name, a CSS selector can be used and this can be one of the retina.js' configurable options. 根据pull请求,不是按标签名称查找<img>元素,而是可以使用CSS选择器,这可以是retina.js的可配置选项之一。 A CSS selector can be created that will filter out user uploaded images (and other images for which a '2x' variant is expected not to exist). 可以创建一个CSS选择器,用于过滤掉用户上传的图像(以及预期不存在'2x'变体的其他图像)。

Another possibility is to add a filter function to the configurable options. 另一种可能性是为可配置选项添加过滤功能。 The function can be called on each matched <img> element; 可以在每个匹配的<img>元素上调用该函数; a return true would cause a '2x' variant to be downloaded and anything else would cause the <img> to be skipped. return true将导致下载“2x”变体,其他任何内容都会导致跳过<img>

The basic, default configuration would change from the current version to something like: 基本的默认配置会从当前版本更改为:

var config = {
  check_mime_type: true,
  retinaImgTagSelector: 'img',
  retinaImgFilterFunc: undefined
};

The Retina.init() function would change from the current version to something like: 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();
  }
};

To put it into practice, before window.onload fires, call: 要在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;
  }
});

Update: Cleaned up and reorganized. 更新:清理并重组。 Added the localStorage enhancement. 添加了localStorage增强功能。

Short answer: Its not possible using client-side JavaScript only 简短回答:仅使用客户端JavaScript是不可能的

After browsing the code, and a little research, It appears to me that retina.js isn't really throwing the 404 errors. 在浏览代码和一些研究之后,在我看来,retina.js并没有真正抛出404错误。

What retina.js is actually doing is requesting a file and simply performing a check on whether or not it exists based on the error code. retina.js实际上正在做的是请求一个文件,只是根据错误代码检查它是否存在。 Which actually means it is asking the browser to check if the file exists. 这实际上意味着它要求浏览器检查文件是否存在。 The browser is what gives you the 404 and there is no cross browser way to prevent that (I say "cross browser" because I only checked webkit). 浏览器是给你404的东西,没有跨浏览器的方式来阻止它(我说“跨浏览器”,因为我只检查了webkit)。

However, what you could do if this really is an issue is do something on the server side to prevent 404s altogether. 但是,如果这确实是一个问题,你可以做的是在服务器端做一些事情来完全防止404s。

Essentially this would be, for example, /retina.php?image= YOUR_URLENCODED_IMAGE_PATH a request to which could return this when a retina image exists... 基本上这将是,例如,/retina.php? image = YOUR_URLENCODED_IMAGE_PATH ,当视网膜图像存在时,可以返回此请求...

{"isRetina": true, "path": "YOUR_RETINA_IMAGE_PATH"}}

and this if it doesnt... 如果它不...

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

You could then have some JavaScript call this script and parse the response as necessary. 然后,您可以使用一些JavaScript调用此脚本并根据需要解析响应。 I'm not claiming that is the only or the best solution, just one that would work. 我并不是说这是唯一或最好的解决方案,只是一个可以工作的解决方案。

Retina JS supports the attribute data-no-retina on the image tag. Retina JS支持图像标签上的属性data-no-retina。 This way it won't try to find the retina image. 这样它就不会试图找到视网膜图像。

Helpful for other people looking for a simple solution. 寻找简单解决方案的其他人很有帮助。

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

I prefer a little more control over which images are replaced. 我更喜欢控制哪些图像被替换。

For all images that I've created a @2x for, I changed the original image name to include @1x. 对于我创建了@ 2x for的所有图像,我将原始图像名称更改为包含@ 1x。 (* See note below.) I changed retina.js slightly, so that it only looks at [name]@1x.[ext] images. (*见下面的注释。)我稍微更改了retina.js,因此它只查看[name] @ 1x。[ext]图像。

I replaced the following line in retina-1.1.0.js: 我在retina-1.1.0.js中替换了以下行:

retinaImages.push(new RetinaImage(image));

With the following lines: 有以下几行:

 if(image.src.match(/@1x\.\w{3}$/)) {
    image.src = image.src.replace(/@1x(\.\w{3})$/,"$1");
    retinaImages.push(new RetinaImage(image));
}

This makes it so that retina.js only replaces @1x named images with @2x named images. 这使得retina.js只用@ 2x命名图像替换@ 1x命名图像。

(* Note: In exploring this, it seems that Safari and Chrome automatically replace @1x images with @2x images, even without retina.js installed. I'm too lazy to track this down, but I'd imagine it's a feature with the latest webkit browsers. As it is, retina.js and the above changes to it are necessary for cross-browser support.) (*注意:在探索这个时,似乎Safari和Chrome会自动用@ 2x图像替换@ 1x图像,即使没有安装retina.js。我也懒得跟踪它,但我想它是一个功能最新的webkit浏览器。实际上,retina.js及其上述更改对于跨浏览器支持是必要的。)

One of solutions is to use PHP: 解决方案之一是使用PHP:

replace code from 1st post with: 从第一篇文章替换代码:

        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();

and in yours site root add file named "image.php": 并在您的站点根目录中添加名为“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 is a nice tool for fixed images on static web pages, but if you are retrieving user uploaded images, the right tool is server side. retina.js是静态网页上固定图像的一个很好的工具,但是如果你要检索用户上传的图像,那么正确的工具就是服务器端。 I imagine PHP here, but the same logic may be applied to any server side language. 我在这里想象PHP,但是同样的逻辑可以应用于任何服务器端语言。

Provided that a nice security habit for uploaded images is to not let users reach them by direct url: if the user succeeds in uploading a malicious script to your server, he should not be able to launch it via an url ( www.yoursite.com/uploaded/mymaliciousscript.php ). 如果上传图像的良好安全习惯是不允许用户通过直接URL访问它们:如果用户成功将恶意脚本上传到您的服务器,他应该无法通过URL启动它( www.yoursite.com/uploaded/mymaliciousscript.php )。 So it is usually a good habit to retrieve uploaded images via some script <img src="get_image.php?id=123456" /> if you can... (and even better, keep the upload folder out of the document root) 所以通过一些脚本<img src="get_image.php?id=123456" />检索上传的图像通常是一个好习惯,如果你可以......(甚至更好,将上传文件夹保留在文档根目录之外)

Now the get_image.php script can get the appropriate image 123456.jpg or 123456@2x.jpg depending on some conditions. 现在get_image.php脚本可以根据某些条件获得适当的图像123456.jpg或123456@2x.jpg。

The approach of http://retina-images.complexcompulsions.com/#setupserver seems perfect for your situation. http://retina-images.complexcompulsions.com/#setupserver的方法似乎非常适合您的情况。

First you set a cookie in your header by loading a file via JS or CSS: 首先,通过JS或CSS加载文件,在标题中设置一个cookie:

Inside HEAD: 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>

At beginning of BODY: 在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>

Now every time your script to retrieve uploaded images is called, it will have a cookie set asking for retina images (or not). 现在每次调用上传图像的脚本时,它都会有一个cookie集,要求提供视网膜图像(或不是)。

Of course you may use the provided retinaimages.php script to output the images, but you may also modify it to accomodate your needs depending on how you produce and retrieve images from a database or hiding the upload directory from users. 当然,您可以使用提供的retinaimages.php脚本输出图像,但您也可以根据您从数据库生成和检索图像或从用户隐藏上载目录的方式对其进行修改以满足您的需求。

So, not only it may load the appropriate image, but if GD2 is installed, and you keep the original uploaded image on the server, it may even resize it and crop accordingly and save the 2 cached image sizes on the server. 因此,它不仅可以加载适当的图像,而且如果安装了GD2,并且您将原始上载的图像保留在服务器上,它甚至可以调整其大小并相应地裁剪并将2个缓存的图像大小保存在服务器上。 Inside the retinaimages.php sources you can see (and copy) how it works: 在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..

?>

Pros: image is loaded only once (retina users on 3G at least won'tload 1x+2x images), works even without JS if cookies are enabled, can be switched on and off easily, no need to use apple naming conventions. 优点:图像只加载一次(3G上的视网膜用户至少不会加载1x + 2x图像),如果启用了cookie,即使没有JS ,也可以轻松打开和关闭,无需使用苹果命名约定。 You load image 12345 and you get the correct DPI for your device. 您加载图像12345,您获得了正确的设备DPI。

With url rewriting you may even render it totally transparent by redirecting /get_image/1234.jpg to /get_image.php?id=1234.jpg 使用url重写,您甚至可以通过将/get_image/1234.jpg重定向到/get_image.php?id=1234.jpg来使其完全透明。

My suggestion is that you recognize the 404 errors to be true errors, and fix them the way that you are supposed to, which is to provide Retina graphics. 我的建议是你认识到404错误是真正的错误,并按照你应该的方式修复它们,即提供Retina图形。 You made your scripts Retina-compatible, but you did not complete the circle by making your graphics workflow Retina-compatible. 您使您的脚本与Retina兼容,但您没有通过使您的图形工作流与Retina兼容来完成该循环。 Therefore, the Retina graphics are actually missing. 因此,Retina图形实际上是丢失的。 Whatever comes in at the start of your graphics workflow, the output of the workflow has to be 2 image files, a low-res and Retina 2x. 无论图形工作流程开始时是什么,工作流程的输出必须是2个图像文件,低分辨率和Retina 2x。

If a user uploads a photo that is 3000x2400, you should consider that to be the Retina version of the photo, mark it 2x, and then use a server-side script to generate a smaller 1500x1200 non-Retina version, without the 2x. 如果用户上传的照片为3000x2400,您应该将其视为照片的Retina版本,将其标记为2x,然后使用服务器端脚本生成较小的1500x1200非Retina版本,而不使用2x。 The 2 files together then constitute one 1500x1200 Retina-compatible image that can be displayed in a Web context at 1500x1200 whether the display is Retina or not. 这两个文件一起构成一个1500x1200 Retina兼容图像,无论显示器是否为Retina,都可以在1500x1200的Web上下文中显示。 You don't have to care because you have a Retina-compatible image and Retina-compatible website. 您不必关心,因为您有与Retina兼容的图像和Retina兼容的网站。 The RetinaJS script is the only one that has to care whether a client is using Retina or not. RetinaJS脚本是唯一一个必须关心客户端是否使用Retina的脚本。 So if you are collecting photos from users, your task is not complete unless you generate 2 files, both low-res and high-res. 因此,如果您要从用户收集照片,除非您生成2个低分辨率和高分辨率的文件,否则您的任务将无法完成。

The typical smartphone captures a photo that is more than 10x the size of the smartphone's display. 典型的智能手机拍摄的照片尺寸是智能手机显示屏的10倍以上。 So you should always have enough pixels. 所以你应该总是有足够的像素。 But if you are getting really small images, like 500px, then you can set a breakpoint in your server-side image-reducing script so that below that, the uploaded photo is used for the low-res version and the script makes a 2x copy that is going to be no better than the non-Retina image but it is going to be Retina-compatible. 但是如果你得到的是非常小的图像,比如500px,那么你可以在你的服务器端图像缩小脚本中设置一个断点,以便在下面,上传的照片用于低分辨率版本,脚本制作2x拷贝这将不会比非Retina图像好,但它将与Retina兼容。

With this solution, your whole problem of “is the 2x image there or not?” goes away, because it is always there. 有了这个解决方案,你的整个问题“是2x图像在哪里?”消失了,因为它总是在那里。 The Retina-compatible website will just happily use your Retina-compatible database of photos without any complaints. 与Retina兼容的网站将很乐意使用您的Retina兼容照片数据库,无任何投诉。

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

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