曦远 (DealiAxy)一份私人的文字刊物

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博客开发笔记(19):Markdown渲染方案探索

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

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

博客上线

终于上线啦! StarBlog博客项目从春节开始,跨越三个季节终于上线了~ 其实服务器这几天已经部署好了,然后我又测试了一会,暂时觉得OK,终于可以和大家分享了~ 博客地址是:blog.deali.cn 欢迎访问哈~ PS:友链模块即将完成,欢迎各位站长大佬们来交换友链~! 系列文章 顺便回顾一下已更新的12篇文章,接下来要介绍RESTFul接口和管理后台的前端开发了,欢迎继续关注~ 基于.Net

2022-06-20 · StarBlog博客开发笔记 · 约 1 分钟

StarBlog博客开发笔记(18):实现本地Typora文章打包上传

前言 九月太忙,只更新了三篇文章,本来这个功能是从九月初就开始做的,结果一直拖到现在国庆假期才有时间完善并且写文章~ 之前我更新了几篇关于 Python 的文章,有朋友留言问是不是不更新 .Net 了,那肯定不能啊,我只能说**「我 全 都 要」**,所以我反手就更新了一篇Asp-Net-Core开发笔记。 然后顺便立个Flag:今年底前完成StarBlog系列文章的主体部分(即API开发+后台前

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

讨论

共 0 条

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

加载评论中…