在 Django 中 如何为提高页面加载速度优化图像
由于随着时间的推移,页面大小一直激增,让网站快速加载成了持久战。回到 2011 年,我们处理的网站平均大小是 700KB,这在当时已经算是有点极端了。
现在,我们处理的网站大小通常是 2MB 或者更高。 那是 Doom 的大小 ,也是 90 年代中期一个视频游戏的大小。
大部分这种膨胀的主要驱动力是图像。在 2011 年 6 月,平均每个网站有 480KB 张图,而 现在平均是 1.4MB 。
显然,图片是主要的性能杀手。但是客户端及其用户通常要求的体验,迫使软件开发者拿出越来越聪明的方法来处理这个问题。
你可以使用 3 种不同的方法来解决这个问题。让我们来看一看。
使用 Sorl 在服务器端动态调整图像大小
当你的网站提供了多张图片时,最容易实现的目标是压缩图片大小,特别是当你的应用允许用户上传图片的时候。用户很少会为显示优化图片。诚然,我们也不应该要求他们这样做。这是用户使用我们的软件需要客户的另一个障碍。因此,当我们要显示那个媒体文件的时候,我们很少想要以我们能够得到的最大分辨率来展示。
例如,当用户上传一个 6016 x 3376,大小为 7MB 的图片时,你真的不应该将相同的分辨率提供给任何用户。这是因为, 根据 w3 schools ,1%的市场中,你可能会获得的最高的普通大小是 2560x1440。实际上,允许的最大大小应该是 1920x1080,因为 那个浏览拥有最大的市场份额 (18%) 。
对于 Django 社区来说,用于减少图像大小的一个流行包是 Sorl-thumbnail 。它生成,然后在服务器端缓存一个合适大小的图像。
安装设置
You can get the code for the latest stable release using 你可以使用'pip'来获取最新稳定。(像往常一样,对于一个真正的项目,一定要使用 requirements 文件 和 virtualenv ,这一般是好的 python 实践。)
$ pip install sorl-thumbnail
然后,找到配置文件,然后在'INSTALLED_APPS'中注册'sorl.thumbnail'。
INSTALLED_APPS = (
...
'sorl.thumbnail',
)
为了获得最佳性能,你真的应该使用 sorl 中的 ImageField,因为当你删除原来的主图像时,它会自动删除相关的缓存图像。但是,如果你选择跳过它,仍能使用主要功能
from django.db import models
from sorl.thumbnail import ImageField
class Thing(models.Model):
image = ImageField(upload_to='thing')
添加到模板
使用 sorl 的模板标签是极为方便的。对于实现,你有多种选择,但最简单的方法是慢慢等待,直到有人访问了一个页面。
一旦页面被访问,它就会为该页面生成一次图像,除非你清除缓存,否则它将一直保有。因此,你需要加载合适的模板标签,以获得这种便利。
对于以下实现,让我们使用以下的一组假设:
- 在项目目录中,你创建了一个基本的模板,用来处理所有的样板 HTML (如 Title, Head, Body 标签)。按惯例,我们称其为
base.html
。 - 有一个名为
recipes
的 Django 应用。 - 在 recipes 应用目录下的合适子目录中,有一个 Detail 模板 (例如,
recipes/templates/recipes/detail.html
)。 - 也假设该模板通过一个 Detail View 进行加载,这个视图添加
recipe
变量到它的上下文,该变量有一个image
属性,这个属性是一个 ImageField 对象。 - 最后,对于所有这些实现,假设你通过使用 100%视口来处理英雄般的图像。对于我们而言,基于我们已经看到的浏览统计,将其宽限制在 1920px。
基本(反模式)实现
以下基本方法是性能最糟糕的,我会将其当成一个反模式。但它正好类似于 Sorl 文档中如何使用 Sorl 库的方法: http://sorl- thumbnail.readthedocs.io/en/latest/examples.html#template-examples
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<img src="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{% endthumbnail
%}" />
{% endblock content %}
再次,虽然这个特定的方法的实现微不足道,但是它有一个巨大的缺点:只处理最大显示尺寸。结果是,对于不需要进行大文件加载的手机极其糟糕。(我看着你,iPhone 和你的小屏幕。)
这种方法聊胜于无,但老实说,你不应该用它,除非时间真的很重要。有一种更好的方式,让我们开始吧。
使用 Srcset 和 Sizes 响应执行
Srcset 和 Sizes 方法是一种棒棒的方式,它让浏览器自己选择想要的的。它相对容易实现,但如果你开始看它的实现,不知道为什么我不使用 Picture 元素来对媒体查询进行增量控制,那么我推荐你读一读 Eric Portis 写的 Srcset 和大小一文 ,了解为什么你不应该那样做。
为了你能简单实用,我已经展示了几种流行的 CSS 框架(例如,Bootstrap 3&4, Foundation 6, 和 Material Design Lite)。然而,这种一般的方法可以很容易的修改以适用于你使用的任何框架。只要找到合适的断点兵处理最大尺寸即可。
需要注意的是,无关框架,对于 src 上的所有图像,我回到 1920px。这样做是因为我相信,你的回退将最有可能发生在使用老的浏览器,但是有一个足够的互联网连接的桌面用户上。所以,我回到一个高分辨率图像。如果对于你的用户群,你不同意这种做法,那么你可以选择任意对你有意义的。
**附注:在写这篇文章的时候,所有浏览器的最新版本都支持该 CSS 属性, 除了 IE11 及后版本。 如果你必须考虑哪些不支持这个属性的浏览器,而你真的想要优化它,那么读一读下面“那些不支持 Srcset 的浏览器怎么办?”这一节。
Bootstrap 4 默认断点
该框架在 544px, 768px, 992px,和 1024px 使用 4 个断点。
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<img src="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{% endthumbnail
%}"
srcset="
{% thumbnail recipe.image '544' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '768' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '992' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1200' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1920' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %}"
alt="Some awesome soup"
sizes="100vw"
/>
{% endblock content %}
Bootstrap 3 默认断点
该框架在 768px, 992px, 和 1024px 使用 3 个断点。
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<img src="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{% endthumbnail
%}"
srcset="
{% thumbnail recipe.image '768' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '992' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1200' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1920' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %}"
alt="Some awesome soup"
sizes="100vw"
/>
{% endblock content %}
Foundation 6 默认断点
该框架在 640px 和 1024px 使用 2 个断点。
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<img src="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{% endthumbnail
%}"
srcset="
{% thumbnail recipe.image '640' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1024' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1920' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %}"
alt="Some awesome soup"
sizes="100vw"
/>
{% endblock content %}
Material Design Lite (MDL) 默认断点
该框架在 480px 和 840px 使用 2 个断点。
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<img src="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{% endthumbnail
%}"
srcset="
{% thumbnail recipe.image '480' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '840' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %},
{% thumbnail recipe.image '1920' as im %} {{ im.url }} {{ im.x }}w{%
endthumbnail %}"
alt="Some awesome soup"
sizes="100vw"
/>
{% endblock content %}
使用 Picture 元素进行响应式实现
所以我之前说你可能不希望使用这种方法。但是,既然你在阅读这部分,那么我希望你真的想知道如何实现 Picture 元素来代替。
为了减轻你的好奇心,这里是使用 Bootstrap 4 断点一些示例代码:
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load thumbnail %}
{% block content %}
<picture>
<source {% thumbnail recipe.image '544' as im %}
srcset="{{ im.url }}”
media="(max-width:{{ im.x }}px)"
{% endthumbnail %}>
<source {% thumbnail recipe.image '768' as im %}
srcset="{{ im.url }}"
media="(min-width:545px)"
{% endthumbnail %}>
<source {% thumbnail recipe.image '992' as im %}
srcset="{{ im.url }}"
media="(min-width:769px)"
{% endthumbnail %}>
<source {% thumbnail recipe.image '1200' as im %}
srcset="{{ im.url }}"
media="(min-width:993px)"
{% endthumbnail %}>
<source {% thumbnail recipe.image '1920' as im %}
srcset="{{ im.url }}"
media="(min-width:1201px)"
{% endthumbnail %}>
<img srcset="{% thumbnail recipe.image '1920' as im %}{{ im.url }}{%
endthumbnail %}"
alt="Some awesome soup">
</picture>
{% endblock content %}
这种方法的缺点是:
- 对于相同的效果,它繁琐得多得多。
- 你要记住,为各种断点进行 1px 抵消。
- 如果你决定重构断点,这将极其脆弱。
当你实际上自适应你的显示图像时,你会选择这种方式。也就是说,你要在不同的断点肯定第选择不同的图片,并且你要特别挑选断点,而不是将其留给浏览器。
那些不支持 Srcset 的浏览器怎么办?
如果你读到这里,你可能会生气,因为现在你知道你真的应该使用 Srcset,但由于你需要针对 IE10 或者也许是较旧版本的 Android 浏览器而不能使用。所以,你将要被诱于使用我认为是反模式的基本实现,因为你认为你没有选择。那么,这就是为你准备的。
使用诸如 Picturefill 这样的填充工具 JavaScript 库。你可以在那里读一读文档,但是这里是直接从他们的文档中找到的实现:
base.html
<head>
. . .
<script>
// Picture element HTML5 shiv
document.createElement( "picture" );
</script>
<script src="picturefill.js" async></script>
</head>
如果添加一点,它就会工作。你的超级老的 Firefox 版本现在可以工作啦。
背景图片呢?
不幸的是,当使用背景图片的时候,对于 srcset 并没有一个真正对应的选秀。因此,基于目前我已经告诉你的东西,你将不得不使用基本实现,并处理最大尺寸,和使用断点。
django-flexible-images 呢?
如果你已经到 Django 社区寻求帮助了,那么你可能会看到 django-flexible-images 。它看起来非常棒,因为它实现起来相对简单。
通过 pip 安装。
$ pip install https://github.com/lewiscollard/django-flexible-images.git
然后,找到你的配置文件,在'INSTALLED_APPS'中注册 storages
。
INSTALLED_APPS = (
...
'flexible_images',
)
接着,添加 js 到基本模板中。
base.html
<head>
. . .
<script type="text/javascript" src="{% static 'flexible-images/flexible-
images.js' %}"></script
</head>
然后,在一个合适的模板中使用它。
recipes/templates/recipes/detail.html
{% extends 'base.html' %}
{% load flexible_images %}
{% block content %}
{% flexible_image recipe.image alt="Some awesome soup" %}
{% endblock content %}
如果你仅是将其与 sorl 进行对比,那么它会短得多,因为它在后台为一堆使用 FLEXIBLE_IMAGE_SIZES
设置的不同大小自动生成,如果你喜欢的话,可以覆盖你的设置。
在这一切之上,它处理背景图片,然后自动生成你所需要的所有大小。这是相当聪明的。
那么,为什么一开始我没用呢?这有些缺点:
- 该项目开始不到一年,并且它只有一个小的用户群 (Github 上包括我只有 4 个 star)
- 只在 Django 1.8 中测试过
- 对于 Python 或者 JavaScript 没有单元测试
- 基本上,它是对 Sorl 的一个非常薄的封装。比之 Python,更多的是 JavaScript 代码。
- 对于如何在 HTML 上实现超级固执己见。
因此,虽然它超级酷,但是,我不能完全赞同它作为你现在应该做的事情。
关于 Sorl 的结论
如果你实现了我上面说的任意一种方式,那么至少应该看看,当你的浏览器加载图片的时候,他们的大小将会更合理。对于你的页面速度,这应该有显著改善。
使用 AWS S3,以获取更好的响应时间和缓存
在 AWS 上实现任何东西应该有它自身提供。我只会告诉你你应该怎么做,而不是为该方法提供大量的解释和防范。
使用 S3,我们的目标是比你从自身服务器更快的传递文件,并在用户机器上尽可能长的缓存图像。
安装 AWS
遵循以下几个步骤:
- 在 https://aws.amazon.com 上创建或登录你的 aws 账户。
- 点击左边的 S3。
- 点击“Create Bucket.”
- 对于 bucket 名字,为你的项目输入合适的名字。对于区域,选择“US Standard.”
- 创建 bucket 之后,点击左上角的 Properties 按钮。
- 展开“Permissions”,并点击“Add CORS Configuration."
- 粘贴以下:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
- 点击 Save 按钮。
Django 中的安装配置
我们将使用 django-storages 来帮助我们处理到刚刚创建的这个 S3 bucket 的连接。对于连接,有一个不错的 boto 封装。所以,还要安装它。
$ pip install django-storages boto
然后,找到你的配置文件,然后在'INSTALLED_APPS'中注册 storages
。
INSTALLED_APPS = (
...
'storages',
)
在相同的配置文件中添加一个配置部分,它看起来像这样:
# ######### AMAZON S3 CONFIGURATION
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
AWS_ACCESS_KEY_ID = '' # TODO: enter aws access key here
AWS_SECRET_ACCESS_KEY = '' # TODO: enter aws secret key here
AWS_STORAGE_BUCKET_NAME = '' # TODO: enter aws bucket name here (note: it
must be all lowercase)
AWS_S3_FILE_OVERWRITE = False # have this set to false if you never have to
worry about updating files with the same name; it just makes it easier
AWS_QUERYSTRING_AUTH = False
AWS_HEADERS = { # see
http://developer.yahoo.com/performance/rules.html#expires
'Expires': 'Thu, 31 Dec 2099 20:00:00 GMT',
'Cache-Control': 'max-age=94608000',
}
# ######### END AMAZON S3 CONFIGURATION
你在上面看到的配置的每一部分对于让你的 S3 存储工作都至关重要。(关于使用 AWS_QUERYSTRING_AUTH 的细节, 阅读 AWS 文档 。然而,对于 AWS_HEADERS 配置设置,该常量对于提高性能很重要。如果你的内容大多数是静态的,并且从不更新,那么你会想要设置 Expires 和 Cache-Control 值为一个相当遥远的时间,正如我上面写的那样。
为了更好的分配使用 AWS Cloudfront
老实说,这是最关键的一步,但如果你正在寻找挤出最后几毫秒的办法,Cloudfront 将有助于完成最后一步,而不仅仅是 S3 从最近的 CDN 节点传输文件。
现在,让我们配置的 Cloudfront。
- 访问 https://console.aws.amazon.com/ 。
- 点击左边的“Cloudfront”。(在 S3 下的右边。)
- 点击“Create Distribution.”
- 在 Web 选项下,点击“Get Started.”
- 你现在应该在第 2 步。
- 对于字段 Origin Domain Name,输入 bucket 的名字。它应该为你自动完成正确的名字。
- 为 Object Caching, Minimum TTL, Maximum TTL, 和 Default TTL 自定义字段,赋值为以秒为单位的长周期。这些将会控制 Cloudfront 检查 S3,看看你的图像是否更新了的频率,对我们来说应该是永远不会。所以如果你愿意,可以设置它们为真正高的值,例如数周。
- 最后,点击右下方的“Create Distribution”。
总结
如果你已经实现了所有这三个建议,那么这里是你应该看看的总结:
- 使用 Sorl,你会创建多种图像分辨率,缓存它们,然后让浏览器选择它所需的一个,这样的话,对于最终用户,图像就不用必须那么大。
- 使用 S3,你的应用服务器就不再需要负责渲染页面和查询,而是将文件发送给 AWS S3,其中,每个文件都会有相应的缓存设置以及最大 TTL。
- 使用 Cloudfront,你会分布存储在 S3 上的图像,这样,用户就可以从最近因此最快的 AWS CDN 节点获取图像。你应该为那些大大下降的资源等待数毫秒连接。
上面所述的这些改进作用是数量级的。因此,如果你需要考虑当务之急是什么,那么顺着列表来即可。
当你完成这些步骤后,你会看到你的页面加载越来越快。这让你的用户和搜索引擎感到高兴,当然,从而也会让你高兴。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论