javascript 在retina.js 库中抑制404

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13704317/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 19:32:44  来源:igfitidea点击:

Suppressing 404s in retina.js library

javascripthttp-status-code-404retina.js

提问by j7nn7k

We use the js lib retina.jswhich swaps low quality images with "retina" images (size times 2). The problem is, that retina.js throws a 404 for every "retina" image which can't be found.

我们使用 js libretina.js,它将低质量图像与“视网膜”图像(大小乘以 2)交换。问题是,对于每个无法找到的“视网膜”图像,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抛出404?

If you don't know the lib. Here is the code throwing the 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();

回答by tiffon

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 XMLHttpRequestrequest. Paths with successful responses are cached in an array and the image is downloaded.

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

The following changes may improve efficiency:

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

  • Failed XMLHttpRequestverification attempts can be cached: Presently, a '2x' path verification attempt is skipped only if it has previously succeeded. 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.

  • Persist '2x' path verification results in localStorage: During initialization, retina.js can check localStoragefor a results cache. 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. Newly encounterd '2x' image paths can be verified and the results added to the cache. Theoretically, while localStorageis available, a 404 will occur only once for an image on a per-browser basis. This would apply across pages for any page on the domain.

  • XMLHttpRequest可以缓存失败的验证尝试:目前,只有在之前成功的情况下才会跳过“2x”路径验证尝试。因此,失败的尝试可能会再次发生。实际上,这并不重要,因为验证过程发生在页面最初加载时。但是,如果结果仍然存在,跟踪故障将防止 404 错误再次发生。

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

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 @2xcharacters and do not exist. See this related answer.

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

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.

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

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

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

Another possibility is to add a filter function to the configurable options. The function can be called on each matched <img>element; a return truewould cause a '2x' variant to be downloaded and anything else would cause the <img>to be skipped.

另一种可能性是向可配置选项添加过滤功能。可以在每个匹配的<img>元素上调用该函数;areturn true会导致“2x”变体被下载,其他任何东西都会导致<img>跳过。

The basic, default configuration would change from the current versionto something like:

基本的默认配置将从当前版本更改为以下内容

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

The Retina.init()function would change from the current versionto 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.onloadfires, 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 localStorageenhancement.

更新:清理和重组。添加了localStorage增强功能。

回答by KTastrophy

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 reallythrowing 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. 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).

Retina.js 实际上做的是请求一个文件,并根据错误代码简单地检查它是否存在。这实际上意味着它要求浏览器检查文件是否存在。浏览器为您提供了 404,并且没有跨浏览器的方法可以防止这种情况(我说“跨浏览器”是因为我只检查了 webkit)。

However, what you could do if this really is an issue is do something on the server side to prevent 404s altogether.

但是,如果这确实是一个问题,您可以做的是在服务器端做一些事情来完全防止 404。

Essentially this would be, for example, /retina.php?image=YOUR_URLENCODED_IMAGE_PATHa 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. I'm not claiming that is the only or the best solution, just one that would work.

然后,您可以让一些 JavaScript 调用此脚本并根据需要解析响应。我并不是说这是唯一或最好的解决方案,只是一个可行的解决方案。

回答by John Ballinger

Retina JS supports the attribute data-no-retina on the image tag. This way it won't try to find the retina image.

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

Helpful for other people looking for a simple solution.

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

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

回答by MrTemple

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. (* See note below.) I changed retina.js slightly, so that it only looks at [name]@1x.[ext] images.

对于我为其创建了 @2x 的所有图像,我将原始图像名称更改为包含 @1x。(* 请参阅下面的注释。)我稍微更改了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})$/,"");
    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 和上面对它的更改是跨浏览器支持所必需的。)

回答by Pawel Pawlowski

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

回答by FrancescoMM

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. I imagine PHP here, but the same logic may be applied to any server side language.

Retina.js 是静态网页上固定图像的好工具,但如果您要检索用户上传的图像,则正确的工具是服务器端。我在这里想象 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). 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)

假设上传图片的一个很好的安全习惯是不要让用户通过直接 url 访问它们:如果用户成功将恶意脚本上传到您的服务器,他应该无法通过 url ( www.yoursite.com/uploaded/mymaliciousscript.php)启动它。所以<img src="get_image.php?id=123456" />如果可以的话,通过一些脚本检索上传的图像通常是一个好习惯......(更好的是,将上传文件夹放在文档根目录之外)

Now the get_image.php script can get the appropriate image 123456.jpg or [email protected] depending on some conditions.

现在 get_image.php 脚本可以根据某些条件获取适当的图像 123456.jpg 或 [email protected]

The approach of http://retina-images.complexcompulsions.com/#setupserverseems 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:

内头:

<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. Inside the retinaimages.php sources you can see (and copy) how it works:

因此,它不仅可以加载适当的图像,而且如果安装了 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..

?>

Pros: image is loaded only once (retina users on 3G at least won'tload 1x+2x images), works even without JSif cookies are enabled, can be switched on and off easily, no need to use apple naming conventions. You load image 12345 and you get the correct DPI for your device.

优点:图像只加载一次(3G 上的视网膜用户至少不会加载 1x+2x 图像),如果启用 cookie ,即使没有 JS也能工作,可以轻松打开和关闭,无需使用苹果命名约定。您加载图像 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 来使其完全透明

回答by Simon White

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. You made your scripts Retina-compatible, but you did not complete the circle by making your graphics workflow Retina-compatible. Therefore, the Retina graphics are actually missing. 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.

我的建议是您将 404 错误识别为真正的错误,并按照您应该的方式修复它们,即提供 Retina 图形。你让你的脚本与 Retina 兼容,但你没有通过让你的图形工作流与 Retina 兼容来完成循环。因此,实际上缺少 Retina 图形。无论在您的图形工作流程开始时出现什么,工作流程的输出都必须是 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. 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. You don't have to care because you have a Retina-compatible image and Retina-compatible website. The RetinaJS script is the only one that has to care whether a client is using Retina or not. So if you are collecting photos from users, your task is not complete unless you generate 2 files, both low-res and high-res.

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

The typical smartphone captures a photo that is more than 10x the size of the smartphone's display. 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.

典型的智能手机拍摄的照片是智能手机显示屏尺寸的 10 倍以上。所以你应该总是有足够的像素。但是如果你得到非常小的图像,比如 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. The Retina-compatible website will just happily use your Retina-compatible database of photos without any complaints.

有了这个解决方案,你的整个问题“是否存在 2x 图像?” 消失了,因为它一直都在。与 Retina 兼容的网站将很乐意使用您与 Retina 兼容的照片数据库,而不会产生任何抱怨。