feat(ImageViewer): add IsIntersectionObserver parameter (#3602)

* refactor: 重构代码消除警告信息

* doc: 代码格式化

* refactor: 脚本所需参数移动到 option 减少 html 渲染

* feat: 增加 IsIntersectionObserver 参数

* doc: 更新示例

* doc: 增加示例

* doc: 更新文档

* test: 增加单元测试

* test: 更新单元测试
This commit is contained in:
Argo Zhang 2024-05-31 10:05:56 +08:00 committed by GitHub
parent a21687cf6d
commit 2c7d9deb35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 69 additions and 18 deletions

View File

@ -85,3 +85,4 @@ dbcell
autoredirect
Searchs
meili
onerror

View File

@ -2,7 +2,9 @@
<a href="@Url">
<div class="card">
<div class="card-header">@Text</div>
<div class="card-body"><img alt="url" src="@ImageUrl" /></div>
<div class="card-body">
<ImageViewer Url="@ImageUrl" IsIntersectionObserver="true" IsAsync="true" />
</div>
</div>
</a>
</div>

View File

@ -127,4 +127,10 @@
<ImagePreviewer @ref="ImagePreviewer" PreviewList="PreviewList"></ImagePreviewer>
</DemoBlock>
<DemoBlock Title="@Localizer["IntersectionObserverTitle"]"
Introduction="@Localizer["IntersectionObserverIntro"]"
Name="IsIntersectionObserver">
<ImageViewer Url="./images/tutorials/waterfall.png" IsIntersectionObserver="true" IsAsync="true" style="height: 400px;"></ImageViewer>
</DemoBlock>
<AttributeTable Items="@GetAttributes()" />

View File

@ -95,6 +95,13 @@ public partial class ImageViewers
ValueList = " — ",
DefaultValue = " — "
},
new() {
Name = nameof(ImageViewer.IsIntersectionObserver),
Description = Localizer["ImageViewersAttrIsIntersectionObserver"],
Type = "bool",
ValueList = "true/false",
DefaultValue = "false"
},
new() {
Name = nameof(ImageViewer.OnLoadAsync),
Description = Localizer["ImageViewersAttrOnLoadAsync"],

View File

@ -5593,6 +5593,7 @@
"ImageViewersAttrPreviewList": "A larger preview link set",
"ImageViewersAttrOnLoadAsync": "The callback method picture when it is loaded successfully",
"ImageViewersAttrOnErrorAsync": "Images load failed callback methods",
"ImageViewersAttrIsIntersectionObserver": "Images lazy loading by view window",
"ImageViewerNormalTips1": "<code>object-fit: fill</code> Fill the default drawing picture to fill the whole container, does not guarantee that keep the original proportion",
"ImageViewerNormalTips2": "<code>object-fit: contain</code> Contains keep original size scaling to ensure the whole image can be appeared in the container, so this parameter may be left blank in the container",
"ImageViewerNormalTips3": "<code>object-fit: cover</code> Cover to keep the original size scaling, width and height of at least one consistent and containers (size small) so this parameter may let image section area is not visible",
@ -5611,7 +5612,9 @@
"ImageViewerErrorTemplateLoadFailed": "Load failed",
"ImagePreviewerTitle": "Use ImagePreviewer Standalone",
"ImagePreviewerIntro": "Pop up the Previewer directly in conjunction with other components like Button",
"ImagePreviewerButton": "Show Previewer"
"ImagePreviewerButton": "Show Previewer",
"IntersectionObserverTitle": "Intersection Observer",
"IntersectionObserverIntro": "By setting <code>IsIntersectionObserver=\"true\"</code> to enable lazy loading, the image will not be loaded when it is in the invisible area, and will be loaded only when it is about to be visible."
},
"BootstrapBlazor.Server.Components.Samples.Geolocations": {
"GeolocationsTitle": "Geolocation",

View File

@ -5593,6 +5593,7 @@
"ImageViewersAttrPreviewList": "预览大图链接集合",
"ImageViewersAttrOnLoadAsync": "图片加载成功时回调方法",
"ImageViewersAttrOnErrorAsync": "图片加载失败时回调方法",
"ImageViewersAttrIsIntersectionObserver": "图片懒加载",
"ImageViewerNormalTips1": "<code>object-fit: fill</code> 填充 默认值 使图片拉伸填满整个容器, 不保证保持原有的比例",
"ImageViewerNormalTips2": "<code>object-fit: contain</code> 包含 保持原有尺寸比例缩放 保证整个图片都可以出现在容器中,因此此参数可能会在容器内留下空白",
"ImageViewerNormalTips3": "<code>object-fit: cover</code> 覆盖 保持原有尺寸比例缩放,宽度和高度至少有一个和容器一致(尺寸小的一致)因此此参数可能会让图片部分区域不可见",
@ -5609,9 +5610,11 @@
"ImageViewerErrorTemplateUrlError": "Url 路径错误",
"ImageViewerErrorTemplateCustom": "自定义",
"ImageViewerErrorTemplateLoadFailed": "加载失败",
"ImagePreviewerTitle": "单独使用ImagePreviewer",
"ImagePreviewerIntro": "配合Button等其他组件直接弹出Previewer",
"ImagePreviewerButton": "显示Previewer"
"ImagePreviewerTitle": "单独使用 ImagePreviewer",
"ImagePreviewerIntro": "配合 <code>Button</code> 等其他组件,直接弹出 <code>ImagePreviewer</code>",
"ImagePreviewerButton": "显示 Previewer",
"IntersectionObserverTitle": "懒加载",
"IntersectionObserverIntro": "通过设置 <code>IsIntersectionObserver=\"true\"</code> 开启懒加载特性,当图片在不可见区域时不加载图片,当图片即将可见时才开始加载图片"
},
"BootstrapBlazor.Server.Components.Samples.Geolocations": {
"GeolocationsTitle": "地理定位/移动距离追踪",

View File

@ -2,7 +2,7 @@
@inherits BootstrapModuleComponentBase
@attribute [BootstrapModuleAutoLoader]
<div @attributes="AdditionalAttributes" class="@ClassString" id="@Id" data-bb-previewer-id="@PreviewerId" data-bb-async="@IsAsyncString">
<div @attributes="AdditionalAttributes" class="@ClassString" id="@Id">
@if (ShowImage)
{
@RenderChildContent()

View File

@ -85,7 +85,7 @@ public partial class ImageViewer
/// </summary>
[Parameter]
public List<string>? PreviewList { get; set; }
/// <summary>
/// 获得/设置 预览大图当前链接集合点开的索引 默认为 0
/// </summary>
@ -110,6 +110,13 @@ public partial class ImageViewer
[Parameter]
public string? FileIcon { get; set; }
/// <summary>
/// 获得/设置 是否交叉监听 默认 false
/// </summary>
/// <remarks>不可见时不加载图片,当图片即将可见时才开始加载图片</remarks>
[Parameter]
public bool IsIntersectionObserver { get; set; }
[Inject]
[NotNull]
private IIconTheme? IconTheme { get; set; }
@ -120,8 +127,6 @@ public partial class ImageViewer
private bool IsError { get; set; }
private string? IsAsyncString => IsAsync ? "true" : null;
/// <summary>
/// <inheritdoc/>
/// </summary>
@ -152,7 +157,7 @@ public partial class ImageViewer
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Url, PreviewList, PreviewIndex);
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, new { Url, PreviewList, PreviewIndex, Async = IsAsync, PreviewerId, Intersection = IsIntersectionObserver });
private RenderFragment RenderChildContent() => builder =>
{
@ -160,7 +165,7 @@ public partial class ImageViewer
{
builder.OpenElement(0, "img");
builder.AddAttribute(1, "class", ImageClassString);
if (!IsAsync)
if (!IsAsync && !IsIntersectionObserver)
{
builder.AddAttribute(2, "src", Url);
}
@ -207,7 +212,7 @@ public partial class ImageViewer
private bool ShouldHandleError => HandleError || ErrorTemplate != null;
private bool ShowPreviewList => PreviewList?.Any() ?? false;
private bool ShowPreviewList => PreviewList != null && PreviewList.Count > 0;
private string PreviewerId => $"prev_{Id}";
private string? PreviewerId => ShowPreviewList ? $"prev_{Id}" : null;
}

View File

@ -14,24 +14,37 @@ const setListeners = (viewer, index) => {
}
}
export function init(id, url, preList, index) {
export function init(id, options) {
const el = document.getElementById(id)
if (el === null) {
return
}
const { url, preList, index, async, previewerId, intersection } = options;
const viewer = {
element: el,
img: el.querySelector('img'),
async: el.getAttribute('data-bb-async'),
async: async,
prevList: preList || [],
previewerId: el.getAttribute('data-bb-previewer-id')
previewerId: previewerId
}
if (url) {
viewer.prevList.push(url)
}
Data.set(id, viewer)
if (viewer.img && viewer.async) {
if (intersection) {
let observer = new IntersectionObserver(enteries => {
const entry = enteries[0];
if (entry.isIntersecting) {
entry.target.setAttribute('src', url);
observer.unobserve(entry.target);
observer = null;
}
});
observer.observe(viewer.img);
}
else if (viewer.img && viewer.async) {
viewer.img.setAttribute('src', url)
}
@ -45,7 +58,7 @@ export function update(id, prevList, index) {
}
viewer.prevList = prevList
setListeners(viewer,index)
setListeners(viewer, index)
}
export function dispose(id) {

View File

@ -122,6 +122,17 @@ public class ImageTest : BootstrapBlazorTestBase
cut.Contains("bb-previewer collapse active");
}
[Fact]
public void IsIntersectionObserver_Ok()
{
var cut = Context.RenderComponent<ImageViewer>(pb =>
{
pb.Add(a => a.Url, "https://www.blazor.zone/images/logo.png");
pb.Add(a => a.IsIntersectionObserver, true);
});
cut.DoesNotContain("src");
}
[Fact]
public void ImagerViewer_Show()
{