返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
StarBlog博客开发笔记

StarBlog Development Notes (20): Image Display Optimization

Preface My server bandwidth is relatively high, so when visiting the blog deployed on it I barely notice any loading delay, and I didn't optimize images. But recently some friends said the blog's images load rather slowly, so let's improve the image optimization~ Currently two things need to be improved Image waterfall layout Image thumbnails Image waterfall layout I introduced the waterfall layout in a previous article: Developing a Blog Project with .NetCore - StarBlog - (10) Image Waterfall Layout But at the time I directly used the Bootstrap5 sample code, which had occasional display misalignment

曦远 (DealiAxy)约 23 分钟
目录7 个章节

Preface

My server bandwidth is relatively high, so when visiting the blog deployed on it I barely notice any loading delay, and I didn't optimize images. But recently some friends said the blog's images load rather slowly, so let's improve the image optimization~

Currently two things need to be improved

  • Image waterfall layout
  • Image thumbnails

Image Waterfall Layout

I introduced the waterfall layout in a previous article: Developing a Blog Project with .NetCore - StarBlog - (10) Image Waterfall Layout

But at the time I directly used the Bootstrap5 sample code, which had a bug where items would occasionally display misaligned.

I checked the masonry-layout official site again and found the cause of this bug: the waterfall layout was rendered before the images had finished loading, and since the images vary in size, this caused the final display to be misaligned.

The fix is also simple: use the imagesLoaded library, which has an event that fires after all images have loaded; re-render the waterfall layout in the event handler, and you're done~

The masonry-layout library doesn't bundle imagesLoaded; you have to install it yourself to use it.

The version I used is 5.0.0

yarn add imagesloaded

Configure the path in gulpfile.js of StarBlog.Web

// 使用 npm 下载的前端组件,自定义存放位置
const customLibs = [
  // ...
  {name: 'imagesloaded', dist: './node_modules/imagesloaded/*.js'},
]

Then run gulp move

Next, modify the photo page StarBlog.Web/Views/Photography/Index.cshtml

Import the JS dependencies. I also used the environment tag helper here, which can include different files based on the environment. In the development environment include the full JS files; in the production environment include the *.min files.

@section bottom {
    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/masonry-layout/dist/masonry.pkgd.js"></script>
        <script src="~/lib/imagesloaded/imagesloaded.pkgd.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="~/lib/jquery/dist/jquery.min.js"></script>
        <script src="~/lib/masonry-layout/dist/masonry.pkgd.min.js"></script>
        <script src="~/lib/imagesloaded/imagesloaded.pkgd.min.js"></script>
    </environment>
    <script src="~/js/photo.js"></script>
}

Change the image list part of the code

<div id="photo-grid" class="row">
  @foreach (var photo in Model.Photos) {
    <div class="col-sm-6 col-lg-4 mb-4 grid-item">
      <partial name="Widgets/PhotoCard" model="photo"/>
    </div>
  }
</div>

Then the JS code in js/photo.js

// init Masonry
let $grid = $('#photo-grid').masonry({
    itemSelector: '.grid-item',
    percentPosition: true,
})
// layout Masonry after each image loads
$grid.imagesLoaded().progress( function() {
    $grid.masonry('layout');
});

Done~

Image Thumbnails

At first I used the SixLabors.ImageSharp.Web library

Pretty convenient, used in the form of middleware

After installing via Nuget, configure it in program.cs

// 注册服务
builder.Services.AddImageSharp();
// 添加中间件
app.UseImageSharp();

After that, you can append parameters like width=xxx&format=xxx to any image file URL to resize and convert the format~

I made each image in the photo list into a partial view component, in the file StarBlog.Web/Views/Shared/Widgets/PhotoCard.cshtml

To turn this into a thumbnail, you just need to modify the img element

Now each image thumbnail has a width of 300 (adaptive width), converted to webp format

<img class="bd-placeholder-img card-img-top" src="/media/@Model.FilePath?width=300&format=webp" alt="">

This middleware has many other parameters you can set; I only used two. See the docs for details: https://docs.sixlabors.com/articles/imagesharp.web/processingcommands.html

Progressive JPEG

Using the ImageSharp.Web middleware was already good enough

But there was one shortcoming: it doesn't support generating progressive JPEG images, so you can't show a blurry outline first and then gradually sharpen when loading an image; instead it loads line by line from top to bottom, which doesn't look great~

So I used the Magick.NET library to generate progressive JPEG images.

This library has three versions: Q8, Q16, Q16-HDRI

According to the official docs, I chose the recommended Q8 version, which has the best performance (image quality doesn't matter anymore)

Install the Magick.NET-Q8-AnyCPU library via nuget

Modify StarBlog.Web/Services/PhotoService.cs

Now I'm going to write a method to generate thumbnails

/// <summary>
/// 生成Progressive JPEG缩略图 (使用 MagickImage)
/// </summary>
public async Task<byte[]> GetThumb(string id, int width=300) {
  var photo = await _photoRepo.Where(a => a.Id == id).FirstAsync();
  using (var image = new MagickImage(GetPhotoFilePath(photo))) {
    image.Format = MagickFormat.Pjpeg;
    image.Resize(width,0);
    return image.ToByteArray();
  }
}

Then write an API

Edit StarBlog.Web/Apis/PhotoController.cs

[HttpGet("{id}/Thumb")]
public async Task<IActionResult> GetThumb(string id, [FromQuery] int width = 300) {
  var data = await _photoService.GetThumb(id, width);
  return new FileContentResult(data, "image/jpeg");
}

Done.

Now you just need to visit /Api/Photo/{图片ID}/Thumb?width=300 to generate a progressive JPEG thumbnail~

Finally, update the StarBlog.Web/Views/Shared/Widgets/PhotoCard.cshtml component

<img class="bd-placeholder-img card-img-top" alt=""
         src="@Url.Action("GetThumb", "Photo", new {id = Model.Id, width = 300})">

okk~

Summary

Compared to the ImageSharp.Web middleware's built-in caching, this thumbnail method I wrote myself is rather crude, and it's generated dynamically on every request, which puts some pressure on the server.

But it doesn't have Progressive JPEG, and this missing feature is really frustrating; I can only hope they implement it soon~

Otherwise I'll have to implement caching myself to improve performance~

Series Articles

References

_EOF_

StarBlog Development Notes (20): Image Display Optimization

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

StarBlog博客开发笔记(27):使用JWT保护接口

前言 这是StarBlog系列在2023年的第二篇更新😂 这几个月都在忙,更新变得很不勤快,但是拖着不更新我的心里更慌,很久没写,要开头就变得很难😑 说回正题,之前的文章里,我们已经把博客关键的接口都开发完成了,但还少了一个最关键的「认证授权」,少了这东西,网站就跟筛子一样,谁都可以来添加和删除数据,乱套了~ 关于「认证授权」的知识,会比较复杂,要学习这块的话,建议分几步: 基础概念 AspN

2023-05-03 · StarBlog博客开发笔记 · 约 1 分钟

StarBlog博客开发笔记(19):Markdown渲染方案探索

前言 笔者认为,一个博客网站,最核心的是阅读体验。 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能。 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做前端渲染,过渡一下。前端渲染我是不满意的,因为性能较差,页面加载出来还会闪一下,有割裂感,影响体验。 现在我已经做出了比较完善的后端渲染方案,前端渲染就可以直接退休了。本文介绍StarBlog博客开发

2022-10-28 · StarBlog博客开发笔记 · 约 1 分钟

StarBlog博客开发笔记(22):开发博客文章相关接口

前言 本文介绍博客文章相关接口的开发,作为接口开发介绍的第一篇,会写得比较详细,以抛砖引玉,后面的其他接口就粗略带过了,着重于WebApi开发的周边设施。 涉及到的接口:文章CRUD、置顶文章、推荐文章等。 开始前先介绍下AspNetCore框架的基础概念,MVC模式(前后端不分离)、WebApi模式(前后端分离),都是有Controller的。 区别在前者的Controller集成自 Contr

2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…