Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(IntersectionObserver): add IntersectionObserver component #3984

Merged
merged 9 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@page "/intersection-observer"
@inject IStringLocalizer<IntersectionObservers> Localizer

<h3>@Localizer["IntersectionObserverTitle"]</h3>

<h4>@Localizer["IntersectionObserverDescription"]</h4>

<DemoBlock Title="@Localizer["IntersectionObserverBaseUsage"]"
Introduction="@Localizer["IntersectionObserverNormalIntro"]"
Name="Normal">
<section ignore>@((MarkupString)Localizer["IntersectionObserverNormalDescription"].Value)</section>
<IntersectionObserver class="bb-list-main scroll" OnIntersectingAsync="OnIntersectingAsync">
@foreach (var image in _images)
{
<IntersectionObserverItem class="bb-list-item">
<img src="@image" />
</IntersectionObserverItem>
})
</IntersectionObserver>
</DemoBlock>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Server.Components.Samples;

/// <summary>
/// 交叉检测组件示例
/// </summary>
public partial class IntersectionObservers
{
private List<string> _images = default!;

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();

_images = Enumerable.Range(1, 100).Select(i => "../../images/default.jpeg").ToList();
}

private Task OnIntersectingAsync(int index)
{
_images[index] = GetImageUrl(index);
StateHasChanged();
return Task.CompletedTask;
}

private static string GetImageUrl(int index) => $"https://picsum.photos/160/160?random={index}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
::deep + .bb-list-main {
display: flex;
flex-wrap: wrap;
height: 500px;
padding: .25rem;
border: var(--bs-border-width) solid var(--bs-border-color);
border-radius: var(--bs-border-radius);
}

::deep + .bb-list-main .bb-list-item {
width: 160px;
height: 160px;
display: flex;
align-items: center;
margin: .25rem;
}

::deep + .bb-list-main .bb-list-item img {
width: 160px;
object-fit: cover;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,12 @@ void AddNotice(DemoMenuItem item)
Url = "light"
},
new()
{
IsNew = true,
Text = Localizer["IntersectionObserver"],
Url = "intersection-observer"
},
new()
{
IsNew = true,
Text = Localizer["Marquee"],
Expand Down
10 changes: 9 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4702,7 +4702,8 @@
"ConnectionService": "ConnectionService",
"ExportPdfButton": "ExportPdfButton",
"ThemeProvider": "IThemeProvider",
"IconPark": "ByteDance IconPark"
"IconPark": "ByteDance IconPark",
"IntersectionObserver": "IntersectionObserver"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "Header grouping function",
Expand Down Expand Up @@ -6587,5 +6588,12 @@
"DriverJsDestroyTitle": "Destroy callback",
"DriverJsDestroyIntro": "Callback method before destruction <code>OnBeforeDestroyAsync</code> or callback method for destruction <code>OnDestroyedAsync</code>",
"DriverJsDestroyDesc": "You can use the <code>OnBeforeDestroyAsync</code> callback to add some logic when the user tries to exit the tour. Prevent destruction when the callback method <code>OnBeforeDestroyAsync</code> returns not <code>NULL</code> string. You can also prevent the user from exiting the tour using <code>AllowClose</code> option. This option is useful when you want to force the user to complete the tour before they can exit."
},
"BootstrapBlazor.Server.Components.Samples.IntersectionObservers": {
"IntersectionObserverTitle": "IntersectionObserver",
"IntersectionObserverDescription": "Scrolling makes the element visible to trigger component callbacks, mostly used for data lazy loading functions",
"IntersectionObserverBaseUsage": "Basic usage",
"IntersectionObserverNormalIntro": "Monitor element visibility changes by setting <code>RootSelector</code> <code>RootMargin</code> <code>Threshold</code> parameter values",
"IntersectionObserverNormalDescription": "This example loads 100 pictures. The invisible pictures load the default pictures (cached). When you scroll to the visible area, you load the real pictures."
}
}
10 changes: 9 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4702,7 +4702,8 @@
"ConnectionService": "在线连接服务 ConnectionService",
"ExportPdfButton": "导出 Pdf 按钮 ExportPdfButton",
"ThemeProvider": "主题服务 IThemeProvider",
"IconPark": "字节跳动图标 IconPark"
"IconPark": "字节跳动图标 IconPark",
"IntersectionObserver": "交叉观察者 IntersectionObserver"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "表头分组功能",
Expand Down Expand Up @@ -6587,5 +6588,12 @@
"DriverJsDestroyTitle": "销毁回调方法",
"DriverJsDestroyIntro": "销毁前回调方法 <code>OnBeforeDestroyAsync</code> 或者销毁回调方法 <code>OnDestroyedAsync</code>",
"DriverJsDestroyDesc": "当用户尝试退出游览时,可以使用 <code>OnBeforeDestroyAsync</code> 回调添加销毁前逻辑,返回 <b>非空字符串</b> 时客户端弹窗二次确认是否阻止销毁;可通过设置 <code>AllowClose</code> 阻止用户退出向导"
},
"BootstrapBlazor.Server.Components.Samples.IntersectionObservers": {
"IntersectionObserverTitle": "IntersectionObserver 可见性观察器",
"IntersectionObserverDescription": "通过滚动使元素可见使触发组件回调,多用于数据懒加载功能",
"IntersectionObserverBaseUsage": "基础用法",
"IntersectionObserverNormalIntro": "通过设置 <code>Root</code> <code>RootMargin</code> <code>Threshold</code> 参数值,监听元素可见性变化",
"IntersectionObserverNormalDescription": "本例加载 100 张图片,不可见图片加载默认图片(已缓存),当滚动到可见区域时,加载真实图片"
}
}
1 change: 1 addition & 0 deletions src/BootstrapBlazor.Server/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"input": "Inputs",
"input-group": "InputGroups",
"ip": "Ips",
"intersection-observer": "IntersectionObservers",
"mask": "Masks",
"markdown": "Markdowns",
"marquee": "Marquees",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]

<div @attributes="AdditionalAttributes" id="@Id" class="@ClassString">
@ChildContent
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/


namespace BootstrapBlazor.Components;

/// <summary>
/// 交叉检测组件
/// </summary>
public partial class IntersectionObserver
{
/// <summary>
/// The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null
/// </summary>
[Parameter]
public string? RootSelector { get; set; }

/// <summary>
/// Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. Defaults to all zeros.
/// </summary>
[Parameter]
public string? RootMargin { get; set; }

/// <summary>
/// Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible.
/// </summary>
[Parameter]
public float Threshold { get; set; }

/// <summary>
/// 获得/设置 已经交叉回调方法
/// </summary>
[Parameter]
public Func<int, Task>? OnIntersectingAsync { get; set; }

/// <summary>
/// 获得/设置 子组件
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

private string? ClassString => CssBuilder.Default("bb-intersection-observer")
.AddClassFromAttributes(AdditionalAttributes)
.Build();

/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnParametersSet()
{
base.OnParametersSet();

if (Threshold < 0 || Threshold > 1)
{
throw new ArgumentOutOfRangeException(nameof(Threshold), $"{nameof(Threshold)} must be between 0 and 1");
}

if (string.IsNullOrEmpty(RootMargin))
{
RootMargin = "0px 0px 0px 0px";
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new { Root = RootSelector, RootMargin, Threshold });

/// <summary>
/// 交叉检测回调方法 由 JavaScript 调用
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
[JSInvokable]
public async Task OnIntersecting(int index)
{
if (OnIntersectingAsync != null)
{
await OnIntersectingAsync(index);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Data from "../../modules/data.js"

export function init(id, invoke, options) {
console.log(options);

const el = document.getElementById(id);
const items = [...el.querySelectorAll(".bb-intersection-observer-item")];
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.unobserve(entry.target);
const index = items.indexOf(entry.target);
invoke.invokeMethodAsync('OnIntersecting', index);
};
});
}, options);

items.forEach(item => observer.observe(item));
Data.set(id, observer);
}

export function dispose(id) {
const observer = Data.get(id);
Data.remove(id);

if (observer) {
observer.disconnect();
observer = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase

<div @attributes="AdditionalAttributes" class="@ClassString">
@ChildContent
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

/// <summary>
/// 检测交叉组件子组件
/// </summary>
public partial class IntersectionObserverItem
{
/// <summary>
/// 获得/设置 子组件
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

private string? ClassString => CssBuilder.Default("bb-intersection-observer-item")
.AddClassFromAttributes(AdditionalAttributes)
.Build();
}