Skip to content

Commit

Permalink
feat(Watermark): add Watermark component (#5099)
Browse files Browse the repository at this point in the history
* feat: 增加水印组件

* feat: 增加样式

* doc: 增加菜单

* doc: 增加路由源码映射

* doc: 增加示例

* refactor: 增加脚本

* feat: 增加防篡改逻辑

* refactor: 增加更新逻辑

* doc: 更新示例

* feat: 增加颜色参数支持

* doc: 更新示例

* feat: 增加 Gap 参数

* doc: 增加默认值

* doc: 更新文档

* refactor: 更新参数数据类型

* doc: 更新示例

* test: 更新单元测试

* chore: bump version 9.2.7
  • Loading branch information
ArgoZhang authored Jan 12, 2025
1 parent 0d7e077 commit ae7d91b
Show file tree
Hide file tree
Showing 13 changed files with 366 additions and 5 deletions.
50 changes: 50 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Watermarks.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@page "/watermark"
@inject IStringLocalizer<Watermarks> Localizer

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

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

<DemoBlock Title="@Localizer["WatermarkNormalTitle"]" Introduction="@Localizer["WatermarkNormalIntro"]" Name="Normal">
<section ignore>
<div class="row form-inline g-3">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Text"></BootstrapInputGroupLabel>
<BootstrapInput @bind-Value="@_text"></BootstrapInput>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="FontSize"></BootstrapInputGroupLabel>
<Slider @bind-Value="@_fontSize" Min="12" Max="20"></Slider>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Color"></BootstrapInputGroupLabel>
<ColorPicker @bind-Value="@_color" IsSupportOpacity="true"></ColorPicker>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Rotate"></BootstrapInputGroupLabel>
<Slider @bind-Value="@_rotate" Min="-180" Max="180"></Slider>
</BootstrapInputGroup>
</div>
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="Gap"></BootstrapInputGroupLabel>
<Slider @bind-Value="@_gap" Min="0" Max="100"></Slider>
</BootstrapInputGroup>
</div>
</div>
</section>
<Watermark Text="@_text" FontSize="@_fontSize" Color="@_color" Rotate="@_rotate"
Gap="@_gap">
<div style="height: 500px; padding-top: 40px; text-align: center;">
<p>this is a watermark demo</p>
<div>这是 watermark 演示</div>
</div>
</Watermark>
</DemoBlock>
22 changes: 22 additions & 0 deletions src/BootstrapBlazor.Server/Components/Samples/Watermarks.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Server.Components.Samples;

/// <summary>
/// Watermarks 组件
/// </summary>
public partial class Watermarks
{
private string _text = "BootstrapBlazor";

private int _fontSize = 16;

private int _gap = 40;

private int _rotate = -40;

private string _color = "#0000004d";
}
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,12 @@ void AddData(DemoMenuItem item)
{
Text = Localizer["Waterfall"],
Url = "tutorials/waterfall"
},
new()
{
IsNew = true,
Text = Localizer["Watermark"],
Url = "watermark"
}
};
AddBadge(item);
Expand Down
9 changes: 8 additions & 1 deletion src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -4787,7 +4787,8 @@
"Player": "Player",
"RDKit": "RDKit",
"SmilesDrawer": "SmilesDrawer",
"Affix": "Affix"
"Affix": "Affix",
"Watermark": "Watermark"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "Header grouping function",
Expand Down Expand Up @@ -6878,5 +6879,11 @@
"AffixPositionTitle": "Position",
"AffixPositionIntro": "Use the parameter <code>Position</code> to control whether the top or bottom is fixed",
"AffixOffsetDesc": "The parameter <code>Position</code> controls whether the top or bottom is fixed, and the <code>Offset</code> value sets the offset to the top or bottom"
},
"BootstrapBlazor.Server.Components.Samples.Watermarks": {
"WatermarkTitle": "Watermark",
"Watermarkntro": "Add specific text or patterns to the page",
"WatermarkNormalTitle": "Basic usage",
"WatermarkNormalIntro": "Use the <code>Text</code> property to set a string to specify the watermark text"
}
}
9 changes: 8 additions & 1 deletion src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -4787,7 +4787,8 @@
"Player": "播放器 Player",
"RDKit": "分子式组件 RDKit",
"SmilesDrawer": "分子式组件 SmilesDrawer",
"Affix": "固钉组件 Affix"
"Affix": "固钉组件 Affix",
"Watermark": "水印组件 Watermark"
},
"BootstrapBlazor.Server.Components.Samples.Table.TablesHeader": {
"TablesHeaderTitle": "表头分组功能",
Expand Down Expand Up @@ -6877,5 +6878,11 @@
"AffixNormalIntro": "固钉默认固定在页面顶部",
"AffixPositionTitle": "位置与距离",
"AffixPositionIntro": "通过参数 <code>Position</code> 控制固定顶端还是底端,通过 <code>Offset</code> 值设置到顶端或者底端距离偏移量"
},
"BootstrapBlazor.Server.Components.Samples.Watermarks": {
"WatermarkTitle": "Watermark 水印组件",
"Watermarkntro": "在页面上添加文本或图片等水印信息",
"WatermarkNormalTitle": "基础用法",
"WatermarkNormalIntro": "使用 <code>Text</code> 属性设置一个字符串指定水印内容"
}
}
3 changes: 2 additions & 1 deletion src/BootstrapBlazor.Server/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@
"player": "Players",
"rdkit": "Rdkits",
"smiles-drawer": "SmilesDrawers",
"affix": "Affixs"
"affix": "Affixs",
"watermark": "Watermarks"
},
"video": {
"table": "BV1ap4y1x7Qn?p=1",
Expand Down
4 changes: 2 additions & 2 deletions src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.2.7-beta05</Version>
<Version>9.2.7</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions src/BootstrapBlazor/Components/Watermark/Watermark.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase
@attribute [BootstrapModuleAutoLoader]

<div @attributes="AdditionalAttributes" id="@Id" class="@ClassString">
@ChildContent
</div>
100 changes: 100 additions & 0 deletions src/BootstrapBlazor/Components/Watermark/Watermark.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License
// See the LICENSE file in the project root for more information.
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone

namespace BootstrapBlazor.Components;

/// <summary>
/// Watermark 组件
/// </summary>
public partial class Watermark
{
/// <summary>
/// 获得/设置 组件内容
/// </summary>
[Parameter]
[EditorRequired]
public RenderFragment? ChildContent { get; set; }

/// <summary>
/// 获得/设置 水印文本 默认 BootstrapBlazor
/// </summary>
[Parameter]
public string? Text { get; set; }

/// <summary>
/// 获得/设置 字体大小 默认 null 未设置 默认使用 16px 字体大小 单位 px
/// </summary>
[Parameter]
public int? FontSize { get; set; }

/// <summary>
/// 获得/设置 颜色 默认 null 未设置
/// </summary>
[Parameter]
public string? Color { get; set; }

/// <summary>
/// 获得/设置 水印的旋转角度 默认 null 45°
/// </summary>
[Parameter]
public int? Rotate { get; set; }

/// <summary>
/// 获得/设置 水印元素的 z-index 值 默认 null
/// </summary>
[Parameter]
public int? ZIndex { get; set; }

/// <summary>
/// 获得/设置 水印之间的间距 值 默认 null
/// </summary>
[Parameter]
public int? Gap { get; set; }

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

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

Text ??= "BootstrapBlazor";
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

if (!firstRender)
{
await InvokeVoidAsync("update", Id, GetOptions());
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, GetOptions());

private object GetOptions() => new
{
Text,
FontSize,
Color,
Rotate,
Gap,
ZIndex
};
}
130 changes: 130 additions & 0 deletions src/BootstrapBlazor/Components/Watermark/Watermark.razor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import Data from "../../modules/data.js"

export function init(id, options) {
const el = document.getElementById(id);
if (el === null) {
return;
}
const watermark = { el, options };
createWatermark(watermark);

const observer = ob => {
ob.observe(el, {
childList: true,
attributes: true,
subtree: true
});
}

const observerCallback = records => {
ob.disconnect();
updateWatermark(records, watermark);
observer(ob);
};

const ob = new MutationObserver(observerCallback);
observer(ob);
watermark.ob = ob;

Data.set(id, watermark);
}

export function update(id, options) {
const watermark = Data.get(id);
watermark.options = options;

createWatermark(watermark);
}

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

if (watermark) {
const { ob } = watermark;
ob.disconnect();

delete watermark.ob;
}
}

const updateWatermark = (records, watermark) => {
for (const record of records) {
for (const dom of record.removedNodes) {
if (dom.classList.contains('bb-watermark-bg')) {
createWatermark(watermark);
return;
}
}

if (record.target.classList.contains('bb-watermark-bg')) {
createWatermark(watermark);
return;
}
}
}

const createWatermark = watermark => {
const { el, options } = watermark;
const defaults = {
gap: 40,
fontSize: 16,
text: 'BootstrapBlazor',
rotate: -40,
color: '#0000004d'
};

for (const key in options) {
if (options[key] === void 0 || options[key] === null) {
delete options[key];
}
}

const bg = getWatermark({ ...defaults, ...options });
const div = document.createElement('div');
const { base64, styleSize } = bg;
div.style.backgroundImage = `url(${base64})`;
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
div.style.backgroundRepeat = 'repeat';
div.style.pointerEvents = 'none';
div.style.opacity = '1';
div.style.position = 'absolute';
div.style.inset = '0';
div.style.zIndex = '999';
div.classList.add("bb-watermark-bg");

const mark = el.querySelector('.bb-watermark-bg');
if (mark) {
mark.remove();
}
el.appendChild(div);
}

const getWatermark = props => {
const canvas = document.createElement('canvas');
const devicePixelRatio = window.devicePixelRatio || 1;

const fontSize = props.fontSize * devicePixelRatio;
const font = fontSize + 'px serif';
const ctx = canvas.getContext('2d');

ctx.font = font;
const { width } = ctx.measureText(props.text);
const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio;
canvas.width = canvasSize;
canvas.height = canvasSize;
ctx.translate(canvas.width / 2, canvas.height / 2);

ctx.rotate((Math.PI / 180) * props.rotate);
ctx.fillStyle = props.color;
ctx.font = font;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

ctx.fillText(props.text, 0, 0);
return {
base64: canvas.toDataURL(),
size: canvasSize,
styleSize: canvasSize / devicePixelRatio,
};
}
3 changes: 3 additions & 0 deletions src/BootstrapBlazor/Components/Watermark/Watermark.razor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.bb-watermark {
position: relative;
}
Loading

0 comments on commit ae7d91b

Please sign in to comment.