├── .EditorConfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── Blazor.Confetti.sln ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── confetti.gif └── icon.png ├── samples ├── KristofferStrube.Blazor.Confetti.WasmSample │ ├── App.razor │ ├── KristofferStrube.Blazor.Confetti.WasmSample.csproj │ ├── Layout │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ └── NavMenu.razor.css │ ├── Pages │ │ ├── Counter.razor │ │ └── Home.razor │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── _Imports.razor │ └── wwwroot │ │ ├── 404.html │ │ ├── css │ │ ├── app.css │ │ └── bootstrap │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ ├── favicon.png │ │ ├── icon-192.png │ │ ├── index.html │ │ └── sample-data │ │ └── weather.json └── KristofferStrube.Blazor.Confetti.WebAppSample │ ├── Components │ ├── App.razor │ ├── Layout │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ └── NavMenu.razor.css │ ├── Pages │ │ ├── Counter.razor │ │ ├── Error.razor │ │ └── Home.razor │ ├── Routes.razor │ └── _Imports.razor │ ├── KristofferStrube.Blazor.Confetti.WebAppSample.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ ├── app.css │ ├── bootstrap │ ├── bootstrap.min.css │ └── bootstrap.min.css.map │ └── favicon.png └── src └── KristofferStrube.Blazor.Confetti ├── Confetti.razor ├── ConfettiAnimator.razor ├── ConfettiOptions.cs ├── ConfettiOriginMode.cs ├── ConfettiPiece.cs ├── ConfettiService.cs ├── ConfettiType.cs ├── DoubleExtensions.cs ├── IServiceExtensions.cs └── KristofferStrube.Blazor.Confetti.csproj /.EditorConfig: -------------------------------------------------------------------------------- 1 | [*] 2 | # All files 3 | dotnet_style_qualification_for_field = false 4 | dotnet_style_qualification_for_property = false 5 | dotnet_style_qualification_for_method = false 6 | dotnet_style_qualification_for_event = false 7 | dotnet_diagnostic.IDE0003.severity = warning 8 | dotnet_style_predefined_type_for_locals_parameters_members = true 9 | dotnet_style_predefined_type_for_member_access = true 10 | dotnet_diagnostic.IDE0049.severity = suggestion 11 | csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async 12 | dotnet_diagnostic.IDE0036.severity = error 13 | dotnet_style_require_accessibility_modifiers = always 14 | dotnet_diagnostic.IDE0040.severity = warning 15 | dotnet_style_readonly_field = true 16 | dotnet_diagnostic.IDE0044.severity = error 17 | csharp_prefer_static_local_function = true 18 | dotnet_diagnostic.IDE0062.severity = warning 19 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 20 | dotnet_diagnostic.IDE0047.severity = warning 21 | dotnet_diagnostic.IDE0048.severity = warning 22 | dotnet_diagnostic.IDE0010.severity = error 23 | dotnet_style_object_initializer = true 24 | dotnet_diagnostic.IDE0017.severity = suggestion 25 | csharp_style_inlined_variable_declaration = true 26 | dotnet_diagnostic.IDE0018.severity = suggestion 27 | dotnet_style_collection_initializer = true 28 | dotnet_diagnostic.IDE0028.severity = warning 29 | dotnet_style_prefer_auto_properties = true 30 | dotnet_diagnostic.IDE0032.severity = suggestion 31 | dotnet_style_explicit_tuple_names = true 32 | dotnet_diagnostic.IDE0033.severity = warning 33 | csharp_prefer_simple_default_expression = false 34 | dotnet_diagnostic.IDE0034.severity = warning 35 | dotnet_style_prefer_inferred_tuple_names = true 36 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 37 | dotnet_diagnostic.IDE0037.severity = suggestion 38 | csharp_style_prefer_local_over_anonymous_function = true 39 | dotnet_diagnostic.IDE0039.severity = warning 40 | csharp_style_deconstructed_variable_declaration = true 41 | dotnet_diagnostic.IDE0042.severity = suggestion 42 | dotnet_style_prefer_conditional_expression_over_assignment = true 43 | dotnet_diagnostic.IDE0045.severity = warning 44 | dotnet_style_prefer_conditional_expression_over_return = true 45 | dotnet_diagnostic.IDE0046.severity = warning 46 | dotnet_style_prefer_compound_assignment = true 47 | dotnet_diagnostic.IDE0054.severity = warning 48 | dotnet_diagnostic.IDE0074.severity = warning 49 | csharp_style_prefer_index_operator = true 50 | dotnet_diagnostic.IDE0056.severity = warning 51 | csharp_style_prefer_range_operator = true 52 | dotnet_diagnostic.IDE0057.severity = warning 53 | dotnet_diagnostic.IDE0070.severity = error 54 | dotnet_style_prefer_simplified_interpolation = true 55 | dotnet_diagnostic.IDE0071.severity = warning 56 | dotnet_diagnostic.IDE0072.severity = error 57 | dotnet_style_prefer_simplified_boolean_expressions = true 58 | dotnet_diagnostic.IDE0075.severity = warning 59 | dotnet_diagnostic.IDE0082.severity = error 60 | csharp_style_implicit_object_creation_when_type_is_apparent = true 61 | dotnet_diagnostic.IDE0090.severity = error 62 | dotnet_diagnostic.IDE0180.severity = warning 63 | csharp_style_namespace_declarations = file_scoped 64 | dotnet_diagnostic.IDE0160.severity = error 65 | dotnet_diagnostic.IDE0161.severity = error 66 | csharp_style_throw_expression = true 67 | dotnet_diagnostic.IDE0016.severity = warning 68 | dotnet_style_coalesce_expression = true 69 | dotnet_diagnostic.IDE0029.severity = warning 70 | dotnet_diagnostic.IDE0030.severity = warning 71 | dotnet_style_null_propagation = true 72 | dotnet_diagnostic.IDE0031.severity = warning 73 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 74 | dotnet_diagnostic.IDE0041.severity = warning 75 | csharp_style_prefer_null_check_over_type_check = true 76 | dotnet_diagnostic.IDE0150.severity = warning 77 | csharp_style_conditional_delegate_call = false 78 | dotnet_diagnostic.IDE1005.severity = warning 79 | csharp_style_var_for_built_in_types = false 80 | csharp_style_var_when_type_is_apparent = true 81 | csharp_style_var_elsewhere = false 82 | dotnet_diagnostic.IDE0007.severity = warning 83 | dotnet_diagnostic.IDE0008.severity = warning 84 | dotnet_diagnostic.IDE0001.severity = error 85 | dotnet_diagnostic.IDE0002.severity = error 86 | dotnet_diagnostic.IDE0004.severity = error 87 | dotnet_diagnostic.IDE0005.severity = error 88 | dotnet_diagnostic.IDE0035.severity = warning 89 | dotnet_diagnostic.IDE0051.severity = warning 90 | dotnet_diagnostic.IDE0052.severity = warning 91 | csharp_style_unused_value_expression_statement_preference = discard_variable 92 | dotnet_diagnostic.IDE0058.severity = warning 93 | csharp_style_unused_value_assignment_preference = discard_variable 94 | dotnet_diagnostic.IDE0059.severity = warning -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish application' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**/README.md' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Checkout the code 15 | - uses: actions/checkout@v2 16 | 17 | # Install .NET 8.0 SDK 18 | - name: Setup .NET 8 preview 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: '8.0.x' 22 | include-prerelease: true 23 | 24 | # Added Ahead-Of-Time workload 25 | - name: Add AOT Workload 26 | run: | 27 | dotnet workload install wasm-tools-net7 28 | dotnet workload restore 29 | 30 | # Generate the website 31 | - name: Publish 32 | run: dotnet publish samples/KristofferStrube.Blazor.Confetti.WasmSample/KristofferStrube.Blazor.Confetti.WasmSample.csproj --configuration Release --output build 33 | 34 | # Publish the website 35 | - name: GitHub Pages action 36 | if: ${{ github.ref == 'refs/heads/main' }} # Publish only when the push is on main 37 | uses: peaceiris/actions-gh-pages@v3.6.1 38 | with: 39 | github_token: ${{ secrets.PUBLISH_TOKEN }} 40 | publish_branch: gh-pages 41 | publish_dir: build/wwwroot 42 | allow_empty_commit: false 43 | keep_files: false 44 | force_orphan: true 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from `dotnet new gitignore` 5 | 6 | # dotenv files 7 | .env 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Ww][Ii][Nn]32/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # Tye 69 | .tye/ 70 | 71 | # ASP.NET Scaffolding 72 | ScaffoldingReadMe.txt 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.iobj 85 | *.pch 86 | *.pdb 87 | *.ipdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *_wpftmp.csproj 98 | *.log 99 | *.tlog 100 | *.vspscc 101 | *.vssscc 102 | .builds 103 | *.pidb 104 | *.svclog 105 | *.scc 106 | 107 | # Chutzpah Test files 108 | _Chutzpah* 109 | 110 | # Visual C++ cache files 111 | ipch/ 112 | *.aps 113 | *.ncb 114 | *.opendb 115 | *.opensdf 116 | *.sdf 117 | *.cachefile 118 | *.VC.db 119 | *.VC.VC.opendb 120 | 121 | # Visual Studio profiler 122 | *.psess 123 | *.vsp 124 | *.vspx 125 | *.sap 126 | 127 | # Visual Studio Trace Files 128 | *.e2e 129 | 130 | # TFS 2012 Local Workspace 131 | $tf/ 132 | 133 | # Guidance Automation Toolkit 134 | *.gpState 135 | 136 | # ReSharper is a .NET coding add-in 137 | _ReSharper*/ 138 | *.[Rr]e[Ss]harper 139 | *.DotSettings.user 140 | 141 | # TeamCity is a build add-in 142 | _TeamCity* 143 | 144 | # DotCover is a Code Coverage Tool 145 | *.dotCover 146 | 147 | # AxoCover is a Code Coverage Tool 148 | .axoCover/* 149 | !.axoCover/settings.json 150 | 151 | # Coverlet is a free, cross platform Code Coverage Tool 152 | coverage*.json 153 | coverage*.xml 154 | coverage*.info 155 | 156 | # Visual Studio code coverage results 157 | *.coverage 158 | *.coveragexml 159 | 160 | # NCrunch 161 | _NCrunch_* 162 | .*crunch*.local.xml 163 | nCrunchTemp_* 164 | 165 | # MightyMoose 166 | *.mm.* 167 | AutoTest.Net/ 168 | 169 | # Web workbench (sass) 170 | .sass-cache/ 171 | 172 | # Installshield output folder 173 | [Ee]xpress/ 174 | 175 | # DocProject is a documentation generator add-in 176 | DocProject/buildhelp/ 177 | DocProject/Help/*.HxT 178 | DocProject/Help/*.HxC 179 | DocProject/Help/*.hhc 180 | DocProject/Help/*.hhk 181 | DocProject/Help/*.hhp 182 | DocProject/Help/Html2 183 | DocProject/Help/html 184 | 185 | # Click-Once directory 186 | publish/ 187 | 188 | # Publish Web Output 189 | *.[Pp]ublish.xml 190 | *.azurePubxml 191 | # Note: Comment the next line if you want to checkin your web deploy settings, 192 | # but database connection strings (with potential passwords) will be unencrypted 193 | *.pubxml 194 | *.publishproj 195 | 196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 197 | # checkin your Azure Web App publish settings, but sensitive information contained 198 | # in these scripts will be unencrypted 199 | PublishScripts/ 200 | 201 | # NuGet Packages 202 | *.nupkg 203 | # NuGet Symbol Packages 204 | *.snupkg 205 | # The packages folder can be ignored because of Package Restore 206 | **/[Pp]ackages/* 207 | # except build/, which is used as an MSBuild target. 208 | !**/[Pp]ackages/build/ 209 | # Uncomment if necessary however generally it will be regenerated when needed 210 | #!**/[Pp]ackages/repositories.config 211 | # NuGet v3's project.json files produces more ignorable files 212 | *.nuget.props 213 | *.nuget.targets 214 | 215 | # Microsoft Azure Build Output 216 | csx/ 217 | *.build.csdef 218 | 219 | # Microsoft Azure Emulator 220 | ecf/ 221 | rcf/ 222 | 223 | # Windows Store app package directories and files 224 | AppPackages/ 225 | BundleArtifacts/ 226 | Package.StoreAssociation.xml 227 | _pkginfo.txt 228 | *.appx 229 | *.appxbundle 230 | *.appxupload 231 | 232 | # Visual Studio cache files 233 | # files ending in .cache can be ignored 234 | *.[Cc]ache 235 | # but keep track of directories ending in .cache 236 | !?*.[Cc]ache/ 237 | 238 | # Others 239 | ClientBin/ 240 | ~$* 241 | *~ 242 | *.dbmdl 243 | *.dbproj.schemaview 244 | *.jfm 245 | *.pfx 246 | *.publishsettings 247 | orleans.codegen.cs 248 | 249 | # Including strong name files can present a security risk 250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 251 | #*.snk 252 | 253 | # Since there are multiple workflows, uncomment next line to ignore bower_components 254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 255 | #bower_components/ 256 | 257 | # RIA/Silverlight projects 258 | Generated_Code/ 259 | 260 | # Backup & report files from converting an old project file 261 | # to a newer Visual Studio version. Backup files are not needed, 262 | # because we have git ;-) 263 | _UpgradeReport_Files/ 264 | Backup*/ 265 | UpgradeLog*.XML 266 | UpgradeLog*.htm 267 | ServiceFabricBackup/ 268 | *.rptproj.bak 269 | 270 | # SQL Server files 271 | *.mdf 272 | *.ldf 273 | *.ndf 274 | 275 | # Business Intelligence projects 276 | *.rdl.data 277 | *.bim.layout 278 | *.bim_*.settings 279 | *.rptproj.rsuser 280 | *- [Bb]ackup.rdl 281 | *- [Bb]ackup ([0-9]).rdl 282 | *- [Bb]ackup ([0-9][0-9]).rdl 283 | 284 | # Microsoft Fakes 285 | FakesAssemblies/ 286 | 287 | # GhostDoc plugin setting file 288 | *.GhostDoc.xml 289 | 290 | # Node.js Tools for Visual Studio 291 | .ntvs_analysis.dat 292 | node_modules/ 293 | 294 | # Visual Studio 6 build log 295 | *.plg 296 | 297 | # Visual Studio 6 workspace options file 298 | *.opt 299 | 300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 301 | *.vbw 302 | 303 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 304 | *.vbp 305 | 306 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 307 | *.dsw 308 | *.dsp 309 | 310 | # Visual Studio 6 technical files 311 | *.ncb 312 | *.aps 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # CodeRush personal settings 330 | .cr/personal 331 | 332 | # Python Tools for Visual Studio (PTVS) 333 | __pycache__/ 334 | *.pyc 335 | 336 | # Cake - Uncomment if you are using it 337 | # tools/** 338 | # !tools/packages.config 339 | 340 | # Tabs Studio 341 | *.tss 342 | 343 | # Telerik's JustMock configuration file 344 | *.jmconfig 345 | 346 | # BizTalk build output 347 | *.btp.cs 348 | *.btm.cs 349 | *.odx.cs 350 | *.xsd.cs 351 | 352 | # OpenCover UI analysis results 353 | OpenCover/ 354 | 355 | # Azure Stream Analytics local run output 356 | ASALocalRun/ 357 | 358 | # MSBuild Binary and Structured Log 359 | *.binlog 360 | 361 | # NVidia Nsight GPU debugger configuration file 362 | *.nvuser 363 | 364 | # MFractors (Xamarin productivity tool) working folder 365 | .mfractor/ 366 | 367 | # Local History for Visual Studio 368 | .localhistory/ 369 | 370 | # Visual Studio History (VSHistory) files 371 | .vshistory/ 372 | 373 | # BeatPulse healthcheck temp database 374 | healthchecksdb 375 | 376 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 377 | MigrationBackup/ 378 | 379 | # Ionide (cross platform F# VS Code tools) working folder 380 | .ionide/ 381 | 382 | # Fody - auto-generated XML schema 383 | FodyWeavers.xsd 384 | 385 | # VS Code files for those working on multiple tools 386 | .vscode/* 387 | !.vscode/settings.json 388 | !.vscode/tasks.json 389 | !.vscode/launch.json 390 | !.vscode/extensions.json 391 | *.code-workspace 392 | 393 | # Local History for Visual Studio Code 394 | .history/ 395 | 396 | # Windows Installer files from build outputs 397 | *.cab 398 | *.msi 399 | *.msix 400 | *.msm 401 | *.msp 402 | 403 | # JetBrains Rider 404 | *.sln.iml 405 | .idea 406 | 407 | ## 408 | ## Visual studio for Mac 409 | ## 410 | 411 | 412 | # globs 413 | Makefile.in 414 | *.userprefs 415 | *.usertasks 416 | config.make 417 | config.status 418 | aclocal.m4 419 | install-sh 420 | autom4te.cache/ 421 | *.tar.gz 422 | tarballs/ 423 | test-results/ 424 | 425 | # Mac bundle stuff 426 | *.dmg 427 | *.app 428 | 429 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 430 | # General 431 | .DS_Store 432 | .AppleDouble 433 | .LSOverride 434 | 435 | # Icon must end with two \r 436 | Icon 437 | 438 | 439 | # Thumbnails 440 | ._* 441 | 442 | # Files that might appear in the root of a volume 443 | .DocumentRevisions-V100 444 | .fseventsd 445 | .Spotlight-V100 446 | .TemporaryItems 447 | .Trashes 448 | .VolumeIcon.icns 449 | .com.apple.timemachine.donotpresent 450 | 451 | # Directories potentially created on remote AFP share 452 | .AppleDB 453 | .AppleDesktop 454 | Network Trash Folder 455 | Temporary Items 456 | .apdisk 457 | 458 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 459 | # Windows thumbnail cache files 460 | Thumbs.db 461 | ehthumbs.db 462 | ehthumbs_vista.db 463 | 464 | # Dump file 465 | *.stackdump 466 | 467 | # Folder config file 468 | [Dd]esktop.ini 469 | 470 | # Recycle Bin used on file shares 471 | $RECYCLE.BIN/ 472 | 473 | # Windows Installer files 474 | *.cab 475 | *.msi 476 | *.msix 477 | *.msm 478 | *.msp 479 | 480 | # Windows shortcuts 481 | *.lnk 482 | 483 | # Vim temporary swap files 484 | *.swp 485 | -------------------------------------------------------------------------------- /Blazor.Confetti.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.Confetti", "src\KristofferStrube.Blazor.Confetti\KristofferStrube.Blazor.Confetti.csproj", "{1E7A2A39-2B64-48D5-BE54-D36F3C3DAE8A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.Confetti.WebAppSample", "samples\KristofferStrube.Blazor.Confetti.WebAppSample\KristofferStrube.Blazor.Confetti.WebAppSample.csproj", "{C3641E65-04A0-45F7-9A19-69F42FB0AA79}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KristofferStrube.Blazor.Confetti.WasmSample", "samples\KristofferStrube.Blazor.Confetti.WasmSample\KristofferStrube.Blazor.Confetti.WasmSample.csproj", "{58B99DE9-BD3A-454D-AF36-3675FBBE9FF1}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1E7A2A39-2B64-48D5-BE54-D36F3C3DAE8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {1E7A2A39-2B64-48D5-BE54-D36F3C3DAE8A}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {1E7A2A39-2B64-48D5-BE54-D36F3C3DAE8A}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {1E7A2A39-2B64-48D5-BE54-D36F3C3DAE8A}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {C3641E65-04A0-45F7-9A19-69F42FB0AA79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C3641E65-04A0-45F7-9A19-69F42FB0AA79}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C3641E65-04A0-45F7-9A19-69F42FB0AA79}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C3641E65-04A0-45F7-9A19-69F42FB0AA79}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {58B99DE9-BD3A-454D-AF36-3675FBBE9FF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {58B99DE9-BD3A-454D-AF36-3675FBBE9FF1}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {58B99DE9-BD3A-454D-AF36-3675FBBE9FF1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {58B99DE9-BD3A-454D-AF36-3675FBBE9FF1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.2.1] - 2024-04-07 10 | ### Fixed 11 | - Fixed that confetti pieces would render for a short time before the animation started. 12 | 13 | ## [0.2.0] - 2024-04-07 14 | ### Added 15 | - Added that `Size`, `SizeVariation` options to `ConfettiOptions` for configuring the size of confetti pieces. 16 | - Added `Type` option to `ConfettiOptions` to configure what the confetti looks like. This currently includes the options `SkewedRectangle`, `Heart`, `Star` and `HandsClapping`. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | Contributing includes many different actions. It is not only writing code. Contributions like discussing solutions in open issues are easily as valuable as contributing code as we can then find the best solution with the perspective of multiple people. 3 | 4 | ## Bugs and feature requests 5 | If you find any bugs in the project or have requests for features, then please [Open a new Issue](https://github.com/KristofferStrube/Blazor.Confetti/issues/new). 6 | 7 | ## Contributing Code and Samples 8 | Before you contribute to the project, you will need to get confirmation from the library author that the contribution is welcome. 9 | This can help align the scope of the contribution so that you and the author agree on the solution and how you ensure the change is maintainable with the existing users in mind. 10 | Once you are ready to start coding try to follow these code guidelines: 11 | - Make a fork of the current `main` branch. 12 | - Follow the existing coding conventions used in the project. This includes tab style, naming conventions, etc. 13 | - If your contribution is a new feature, try to add a demo that demonstrates how this will be used in the sample project. 14 | - Any code or sample you share as a part of the resulting Pull Request should fall under the MIT license agreement. 15 | - You don't need to update the version number of the project as the maintainer will do this when making the next release after the Pull Request has been merged. 16 | - Keep your Pull Request to the point. I.e., if your Pull Request is related to fixing a bug then try not to touch any other files than the ones related to that issue as this will make the chance of the PR being merged without change requests more likely. 17 | 18 | ## Submitting a Pull Request 19 | If you don't know what a pull request is, read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can be built and that the related sample project still works as intended. It is also a good idea to familiarize yourself with the project workflow and our coding conventions. 20 | 21 | ## Ensuring that your contribution will be accepted 22 | You might also read these two blog posts on contributing code: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don't "Push" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik. These blog posts highlight good open-source collaboration etiquette and help align expectations between you and us. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kristoffer Strube 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](/LICENSE) 2 | [![GitHub issues](https://img.shields.io/github/issues/KristofferStrube/Blazor.Confetti)](https://github.com/KristofferStrube/Blazor.Confetti/issues) 3 | [![GitHub forks](https://img.shields.io/github/forks/KristofferStrube/Blazor.Confetti)](https://github.com/KristofferStrube/Blazor.Confetti/network/members) 4 | [![GitHub stars](https://img.shields.io/github/stars/KristofferStrube/Blazor.Confetti)](https://github.com/KristofferStrube/Blazor.Confetti/stargazers) 5 | [![NuGet Downloads (official NuGet)](https://img.shields.io/nuget/dt/KristofferStrube.Blazor.Confetti?label=NuGet%20Downloads)](https://www.nuget.org/packages/KristofferStrube.Blazor.Confetti/) 6 | 7 | # Blazor.Confetti 8 | A small service that can make confetti in your Blazor application. Works for both WASM and Server render mode. 9 | 10 | ![Showcase](./docs/confetti.gif?raw=true) 11 | 12 | # Demo 13 | The sample project can be demoed at https://kristofferstrube.github.io/Blazor.Confetti/ 14 | 15 | On each page, you can find the corresponding code for the example in the top right corner. 16 | 17 | # Getting Started 18 | ## Prerequisites 19 | You need to install .NET 8.0 or newer to use the library. 20 | 21 | [Download .NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) 22 | 23 | ## Installation 24 | You can install the package via NuGet with the Package Manager in your IDE or alternatively using the command line: 25 | ```bash 26 | dotnet add package KristofferStrube.Blazor.Confetti 27 | ``` 28 | 29 | # Usage 30 | The package can be used in Blazor WebAssembly, Blazor Server, and Blazor WebApp projects both with interactive WASM and Server render modes. 31 | ## Import 32 | You need to reference the package in order to use it in your pages. This can be done in `_Import.razor` by adding the following. 33 | ```razor 34 | @using KristofferStrube.Blazor.Confetti 35 | ``` 36 | 37 | ## Add to service collection 38 | The library has one service which is the `ConfettiService` which can be used to start a confetti animation. An easy way to make the service available on all your pages is by registering it in the `IServiceCollection` so that it can be dependency injected in the pages that need it. This is done in `Program.cs` by using our extension `AddConfettiService()` before you build the host like we do in the following code block. 39 | ```csharp 40 | using KristofferStrube.Blazor.Confetti; 41 | 42 | WebApplicationBuilder builder = WebApplication.CreateBuilder(args); 43 | 44 | // Adding other services 45 | 46 | // Adding our service 47 | builder.Services.AddConfettiService(); 48 | 49 | WebApplication app = builder.Build(); 50 | 51 | // Configure middleware 52 | 53 | app.Run(); 54 | ``` 55 | 56 | ## Renderer 57 | For the confetti to appear somewhere we also need to place a component at the root of our application which will work as the drawing space for the confetti animation. A good place to do this could be in `MainLayout.razor` after all other markup. If you are creating a Blazor WebApp you might need to add the attribute `@rendermode="InteractiveServer"` to the `Confetti` component to make it interactive. 58 | ```razor 59 | @inherits LayoutComponentBase 60 |
61 | 64 | 65 |
66 |
67 | About 68 |
69 | 70 |
71 | @Body 72 |
73 |
74 |
75 | 76 | 77 | 78 | ``` 79 | 80 | ## Injecting and activating the confetti service 81 | Now we are ready to inject the `ConfettiService` in one of our pages or components to make some confetti. 82 | ```razor 83 | @inject ConfettiService ConfettiService 84 | 85 | 86 | 87 | @code { 88 | private void Activate() 89 | { 90 | ConfettiService.Activate(new()); 91 | } 92 | } 93 | ``` 94 | 95 | With the above we create confetti from the bottom of the page with all the default settings. We can further customize the confetti colors, amount, origin, speed, and speed variation. 96 | ```razor 97 | @inject ConfettiService ConfettiService 98 | 99 | 100 | 101 | @code { 102 | private ElementReference button; 103 | 104 | private void Activate() 105 | { 106 | ConfettiOptions options = new() 107 | { 108 | Colors = ["silver", "gold", "#B87333"], 109 | Pieces = 500, 110 | Mode = ConfettiOriginMode.FromElement, 111 | Origin = button, 112 | Milliseconds = 2000, 113 | VariationInMilliseconds = 500 114 | }; 115 | 116 | ConfettiService.Activate(options); 117 | } 118 | } 119 | ``` 120 | 121 | 122 | # Related repositories 123 | The library uses the following other packages to support its features: 124 | - https://github.com/KristofferStrube/Blazor.SVGAnimation (To start and stop SVG animations) 125 | 126 | # Related articles 127 | This repository was built with inspiration and help from the following series of articles: 128 | 129 | - [Wrapping JavaScript libraries in Blazor WebAssembly/WASM](https://blog.elmah.io/wrapping-javascript-libraries-in-blazor-webassembly-wasm/) 130 | - [Blazor WASM 404 error and fix for GitHub Pages](https://blog.elmah.io/blazor-wasm-404-error-and-fix-for-github-pages/) 131 | - [How to fix Blazor WASM base path problems](https://blog.elmah.io/how-to-fix-blazor-wasm-base-path-problems/) 132 | -------------------------------------------------------------------------------- /docs/confetti.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.Confetti/cb2562c8411705a6e5784132e47e5962df788e2d/docs/confetti.gif -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.Confetti/cb2562c8411705a6e5784132e47e5962df788e2d/docs/icon.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/KristofferStrube.Blazor.Confetti.WasmSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | true 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @inject NavigationManager NavigationManager 3 | 4 |
5 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 | 16 |
17 | @Body 18 |
19 |
20 |
21 | 22 | 23 | 24 | @code { 25 | private string relativeUri => NavigationManager.ToBaseRelativePath(NavigationManager.Uri); 26 | 27 | protected string page => (string.IsNullOrEmpty(relativeUri) ? "Home" : relativeUri) + ".razor"; 28 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 | 24 | 25 | @code { 26 | private bool collapseNavMenu = true; 27 | 28 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 29 | 30 | private void ToggleNavMenu() 31 | { 32 | collapseNavMenu = !collapseNavMenu; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .bi { 15 | display: inline-block; 16 | position: relative; 17 | width: 1.25rem; 18 | height: 1.25rem; 19 | margin-right: 0.75rem; 20 | top: -1px; 21 | background-size: cover; 22 | } 23 | 24 | .bi-house-door-fill-nav-menu { 25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); 26 | } 27 | 28 | .bi-plus-square-fill-nav-menu { 29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); 30 | } 31 | 32 | .bi-list-nested-nav-menu { 33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); 34 | } 35 | 36 | .nav-item { 37 | font-size: 0.9rem; 38 | padding-bottom: 0.5rem; 39 | } 40 | 41 | .nav-item:first-of-type { 42 | padding-top: 1rem; 43 | } 44 | 45 | .nav-item:last-of-type { 46 | padding-bottom: 1rem; 47 | } 48 | 49 | .nav-item ::deep a { 50 | color: #d7d7d7; 51 | border-radius: 4px; 52 | height: 3rem; 53 | display: flex; 54 | align-items: center; 55 | line-height: 3rem; 56 | } 57 | 58 | .nav-item ::deep a.active { 59 | background-color: rgba(255,255,255,0.37); 60 | color: white; 61 | } 62 | 63 | .nav-item ::deep a:hover { 64 | background-color: rgba(255,255,255,0.1); 65 | color: white; 66 | } 67 | 68 | @media (min-width: 641px) { 69 | .navbar-toggler { 70 | display: none; 71 | } 72 | 73 | .collapse { 74 | /* Never collapse the sidebar for wide screens */ 75 | display: block; 76 | } 77 | 78 | .nav-scrollable { 79 | /* Allow sidebar to scroll for tall menus */ 80 | height: calc(100vh - 3.5rem); 81 | overflow-y: auto; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 | Counter 4 | 5 |

Counter

6 | 7 |

Current count: @currentCount

8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | @code { 29 | private ElementReference button; 30 | 31 | [Inject] 32 | public required ConfettiService ConfettiService { get; set; } 33 | 34 | private int currentCount = 0; 35 | 36 | private void FromBottom() 37 | { 38 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromBottomCenter }); 39 | currentCount++; 40 | } 41 | 42 | private void WithCustomColors() 43 | { 44 | ConfettiService.Activate(new() 45 | { 46 | Mode = ConfettiOriginMode.FromBottomCenter, 47 | Colors = ["silver", "gold", "#B87333"], 48 | Size = 10, 49 | SizeVariation = 5 50 | }); 51 | currentCount++; 52 | } 53 | 54 | private void Slowly() 55 | { 56 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromBottomCenter, Milliseconds = 3000, VariationInMilliseconds = 500 }); 57 | currentCount++; 58 | } 59 | 60 | private void Heart() 61 | { 62 | ConfettiService.Activate(new() 63 | { 64 | Type = ConfettiType.Heart, 65 | Size = 30, 66 | SizeVariation = 30, 67 | Mode = ConfettiOriginMode.FromTopCurtain, 68 | Milliseconds = 3000, 69 | VariationInMilliseconds = 500, 70 | Pieces = 50 71 | }); 72 | currentCount++; 73 | } 74 | 75 | private void Clapping() 76 | { 77 | ConfettiService.Activate(new() 78 | { 79 | Type = ConfettiType.HandsClapping, 80 | Size = 200, 81 | SizeVariation = 0, 82 | Mode = ConfettiOriginMode.FromBottomCenter, 83 | Milliseconds = 2000, 84 | VariationInMilliseconds = 0, 85 | Pieces = 5 86 | }); 87 | currentCount++; 88 | } 89 | 90 | private void Stars() 91 | { 92 | ConfettiService.Activate(new() 93 | { 94 | Type = ConfettiType.Star, 95 | Size = 30, 96 | SizeVariation = 30, 97 | Mode = ConfettiOriginMode.FromTopCurtain, 98 | Milliseconds = 2000, 99 | VariationInMilliseconds = 500, 100 | Pieces = 100 101 | }); 102 | currentCount++; 103 | } 104 | 105 | private void FromButton() 106 | { 107 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromElement, Origin = button }); 108 | currentCount++; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Home 4 | 5 |

Hello, world!

6 | 7 | Welcome to this demo app. Try out the new counter demo with some more power. -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Program.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.Confetti; 2 | using KristofferStrube.Blazor.Confetti.WasmSample; 3 | using Microsoft.AspNetCore.Components.Web; 4 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 5 | 6 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 7 | builder.RootComponents.Add("#app"); 8 | builder.RootComponents.Add("head::after"); 9 | 10 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 11 | 12 | builder.Services.AddConfettiService(); 13 | 14 | await builder.Build().RunAsync(); 15 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:30084", 8 | "sslPort": 44386 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5269", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 27 | "applicationUrl": "https://localhost:7081;http://localhost:5269", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using KristofferStrube.Blazor.Confetti.WasmSample 10 | @using KristofferStrube.Blazor.Confetti.WasmSample.Layout 11 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/404.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Blazor Confetti 6 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | a, .btn-link { 10 | color: #0071c1; 11 | } 12 | 13 | .btn-primary { 14 | color: #fff; 15 | background-color: #1b6ec2; 16 | border-color: #1861ac; 17 | } 18 | 19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 21 | } 22 | 23 | .content { 24 | padding-top: 1.1rem; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid red; 33 | } 34 | 35 | .validation-message { 36 | color: red; 37 | } 38 | 39 | #blazor-error-ui { 40 | background: lightyellow; 41 | bottom: 0; 42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 43 | display: none; 44 | left: 0; 45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 46 | position: fixed; 47 | width: 100%; 48 | z-index: 1000; 49 | } 50 | 51 | #blazor-error-ui .dismiss { 52 | cursor: pointer; 53 | position: absolute; 54 | right: 0.75rem; 55 | top: 0.5rem; 56 | } 57 | 58 | .blazor-error-boundary { 59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 60 | padding: 1rem 1rem 1rem 3.7rem; 61 | color: white; 62 | } 63 | 64 | .blazor-error-boundary::after { 65 | content: "An error has occurred." 66 | } 67 | 68 | .loading-progress { 69 | position: relative; 70 | display: block; 71 | width: 8rem; 72 | height: 8rem; 73 | margin: 20vh auto 1rem auto; 74 | } 75 | 76 | .loading-progress circle { 77 | fill: none; 78 | stroke: #e0e0e0; 79 | stroke-width: 0.6rem; 80 | transform-origin: 50% 50%; 81 | transform: rotate(-90deg); 82 | } 83 | 84 | .loading-progress circle:last-child { 85 | stroke: #1b6ec2; 86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 87 | transition: stroke-dasharray 0.05s ease-in-out; 88 | } 89 | 90 | .loading-progress-text { 91 | position: absolute; 92 | text-align: center; 93 | font-weight: bold; 94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 95 | } 96 | 97 | .loading-progress-text:after { 98 | content: var(--blazor-load-percentage-text, "Loading"); 99 | } 100 | 101 | code { 102 | color: #c02d76; 103 | } 104 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.Confetti/cb2562c8411705a6e5784132e47e5962df788e2d/samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/favicon.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.Confetti/cb2562c8411705a6e5784132e47e5962df788e2d/samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/icon-192.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blazor Confetti 8 | 9 | 10 | 32 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 |
56 |
57 | 58 |
59 | An unhandled error has occurred. 60 | Reload 61 | 🗙 62 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WasmSample/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2022-01-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2022-01-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2022-01-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2022-01-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2022-01-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @inject NavigationManager NavigationManager 3 | 4 |
5 | 8 | 9 |
10 |
11 | 12 | 13 | 14 |
15 | 16 |
17 | @Body 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | An unhandled error has occurred. 26 | Reload 27 | 🗙 28 |
29 | 30 | @code { 31 | private string relativeUri => NavigationManager.ToBaseRelativePath(NavigationManager.Uri); 32 | 33 | protected string page => (string.IsNullOrEmpty(relativeUri) ? "Home" : relativeUri) + ".razor"; 34 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | 79 | #blazor-error-ui { 80 | background: lightyellow; 81 | bottom: 0; 82 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 83 | display: none; 84 | left: 0; 85 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 86 | position: fixed; 87 | width: 100%; 88 | z-index: 1000; 89 | } 90 | 91 | #blazor-error-ui .dismiss { 92 | cursor: pointer; 93 | position: absolute; 94 | right: 0.75rem; 95 | top: 0.5rem; 96 | } 97 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | appearance: none; 3 | cursor: pointer; 4 | width: 3.5rem; 5 | height: 2.5rem; 6 | color: white; 7 | position: absolute; 8 | top: 0.5rem; 9 | right: 1rem; 10 | border: 1px solid rgba(255, 255, 255, 0.1); 11 | background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); 12 | } 13 | 14 | .navbar-toggler:checked { 15 | background-color: rgba(255, 255, 255, 0.5); 16 | } 17 | 18 | .top-row { 19 | height: 3.5rem; 20 | background-color: rgba(0,0,0,0.4); 21 | } 22 | 23 | .navbar-brand { 24 | font-size: 1.1rem; 25 | } 26 | 27 | .bi { 28 | display: inline-block; 29 | position: relative; 30 | width: 1.25rem; 31 | height: 1.25rem; 32 | margin-right: 0.75rem; 33 | top: -1px; 34 | background-size: cover; 35 | } 36 | 37 | .bi-house-door-fill-nav-menu { 38 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); 39 | } 40 | 41 | .bi-plus-square-fill-nav-menu { 42 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); 43 | } 44 | 45 | .bi-list-nested-nav-menu { 46 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); 47 | } 48 | 49 | .nav-item { 50 | font-size: 0.9rem; 51 | padding-bottom: 0.5rem; 52 | } 53 | 54 | .nav-item:first-of-type { 55 | padding-top: 1rem; 56 | } 57 | 58 | .nav-item:last-of-type { 59 | padding-bottom: 1rem; 60 | } 61 | 62 | .nav-item ::deep .nav-link { 63 | color: #d7d7d7; 64 | background: none; 65 | border: none; 66 | border-radius: 4px; 67 | height: 3rem; 68 | display: flex; 69 | align-items: center; 70 | line-height: 3rem; 71 | width: 100%; 72 | } 73 | 74 | .nav-item ::deep a.active { 75 | background-color: rgba(255,255,255,0.37); 76 | color: white; 77 | } 78 | 79 | .nav-item ::deep .nav-link:hover { 80 | background-color: rgba(255,255,255,0.1); 81 | color: white; 82 | } 83 | 84 | .nav-scrollable { 85 | display: none; 86 | } 87 | 88 | .navbar-toggler:checked ~ .nav-scrollable { 89 | display: block; 90 | } 91 | 92 | @media (min-width: 641px) { 93 | .navbar-toggler { 94 | display: none; 95 | } 96 | 97 | .nav-scrollable { 98 | /* Never collapse the sidebar for wide screens */ 99 | display: block; 100 | 101 | /* Allow sidebar to scroll for tall menus */ 102 | height: calc(100vh - 3.5rem); 103 | overflow-y: auto; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | @rendermode InteractiveServer 3 | 4 | Counter 5 | 6 |

Counter

7 | 8 |

Current count: @currentCount

9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | @code { 30 | private ElementReference button; 31 | 32 | [Inject] 33 | public required ConfettiService ConfettiService { get; set; } 34 | 35 | private int currentCount = 0; 36 | 37 | private void FromBottom() 38 | { 39 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromBottomCenter }); 40 | currentCount++; 41 | } 42 | 43 | private void WithCustomColors() 44 | { 45 | ConfettiService.Activate(new() 46 | { 47 | Mode = ConfettiOriginMode.FromBottomCenter, 48 | Colors = ["silver", "gold", "#B87333"], 49 | Size = 10, 50 | SizeVariation = 5 51 | }); 52 | currentCount++; 53 | } 54 | 55 | private void Slowly() 56 | { 57 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromBottomCenter, Milliseconds = 3000, VariationInMilliseconds = 500 }); 58 | currentCount++; 59 | } 60 | 61 | private void Heart() 62 | { 63 | ConfettiService.Activate(new() 64 | { 65 | Type = ConfettiType.Heart, 66 | Size = 30, 67 | SizeVariation = 30, 68 | Mode = ConfettiOriginMode.FromTopCurtain, 69 | Milliseconds = 3000, 70 | VariationInMilliseconds = 500, 71 | Pieces = 50 72 | }); 73 | currentCount++; 74 | } 75 | 76 | private void Clapping() 77 | { 78 | ConfettiService.Activate(new() 79 | { 80 | Type = ConfettiType.HandsClapping, 81 | Size = 200, 82 | SizeVariation = 0, 83 | Mode = ConfettiOriginMode.FromBottomCenter, 84 | Milliseconds = 2000, 85 | VariationInMilliseconds = 0, 86 | Pieces = 5 87 | }); 88 | currentCount++; 89 | } 90 | 91 | private void Stars() 92 | { 93 | ConfettiService.Activate(new() 94 | { 95 | Type = ConfettiType.Star, 96 | Size = 30, 97 | SizeVariation = 30, 98 | Mode = ConfettiOriginMode.FromTopCurtain, 99 | Milliseconds = 2000, 100 | VariationInMilliseconds = 500, 101 | Pieces = 100 102 | }); 103 | currentCount++; 104 | } 105 | 106 | private void FromButton() 107 | { 108 | ConfettiService.Activate(new() { Mode = ConfettiOriginMode.FromElement, Origin = button }); 109 | currentCount++; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Pages/Error.razor: -------------------------------------------------------------------------------- 1 | @page "/Error" 2 | @using System.Diagnostics 3 | 4 | Error 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (ShowRequestId) 10 | { 11 |

12 | Request ID: @RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | 27 | @code{ 28 | [CascadingParameter] 29 | private HttpContext? HttpContext { get; set; } 30 | 31 | private string? RequestId { get; set; } 32 | private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 33 | 34 | protected override void OnInitialized() => 35 | RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; 36 | } 37 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Home 4 | 5 |

Hello, world!

6 | 7 | Welcome to this demo app. Try out the new counter demo with some more power. -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/Routes.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using KristofferStrube.Blazor.Confetti.WebAppSample 10 | @using KristofferStrube.Blazor.Confetti.WebAppSample.Components 11 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/KristofferStrube.Blazor.Confetti.WebAppSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Program.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.Confetti; 2 | using KristofferStrube.Blazor.Confetti.WebAppSample.Components; 3 | 4 | WebApplicationBuilder builder = WebApplication.CreateBuilder(args); 5 | 6 | // Add services to the container. 7 | builder.Services 8 | .AddRazorComponents() 9 | .AddInteractiveServerComponents(); 10 | 11 | builder.Services.AddConfettiService(); 12 | 13 | WebApplication app = builder.Build(); 14 | 15 | // Configure the HTTP request pipeline. 16 | if (!app.Environment.IsDevelopment()) 17 | { 18 | app.UseExceptionHandler("/Error", createScopeForErrors: true); 19 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 20 | app.UseHsts(); 21 | } 22 | 23 | app.UseHttpsRedirection(); 24 | 25 | app.UseStaticFiles(); 26 | app.UseAntiforgery(); 27 | 28 | app.MapRazorComponents() 29 | .AddInteractiveServerRenderMode(); 30 | 31 | app.Run(); 32 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:23347", 8 | "sslPort": 44377 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5258", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7212;http://localhost:5258", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/wwwroot/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | a, .btn-link { 6 | color: #006bb7; 7 | } 8 | 9 | .btn-primary { 10 | color: #fff; 11 | background-color: #1b6ec2; 12 | border-color: #1861ac; 13 | } 14 | 15 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 16 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 17 | } 18 | 19 | .content { 20 | padding-top: 1.1rem; 21 | } 22 | 23 | h1:focus { 24 | outline: none; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid #e50000; 33 | } 34 | 35 | .validation-message { 36 | color: #e50000; 37 | } 38 | 39 | .blazor-error-boundary { 40 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 41 | padding: 1rem 1rem 1rem 3.7rem; 42 | color: white; 43 | } 44 | 45 | .blazor-error-boundary::after { 46 | content: "An error has occurred." 47 | } 48 | 49 | .darker-border-checkbox.form-check-input { 50 | border-color: #929292; 51 | } 52 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.Confetti.WebAppSample/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.Confetti/cb2562c8411705a6e5784132e47e5962df788e2d/samples/KristofferStrube.Blazor.Confetti.WebAppSample/wwwroot/favicon.png -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/Confetti.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.DOM.Extensions 2 | @using Microsoft.JSInterop 3 | @implements IDisposable 4 | 5 | 6 | @foreach (var confettiPiece in confettiPieces) 7 | { 8 | 9 | } 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | @code { 327 | private double width; 328 | private double height; 329 | private bool playing = false; 330 | 331 | [Inject] 332 | public required IJSRuntime JSRuntime { get; set; } 333 | 334 | [Inject] 335 | public required ConfettiService ConfettiService { get; set; } 336 | 337 | List confettiPieces = new(); 338 | 339 | protected override async Task OnAfterRenderAsync(bool firstRender) 340 | { 341 | if (!firstRender) return; 342 | 343 | ConfettiService.Activated += HandleActivated; 344 | 345 | IJSObjectReference windowReference = await JSRuntime.InvokeAsync("window.valueOf"); 346 | 347 | var helper = await JSRuntime.GetHelperAsync(); 348 | width = await helper.InvokeAsync("getAttribute", windowReference, "innerWidth"); 349 | height = await helper.InvokeAsync("getAttribute", windowReference, "innerHeight"); 350 | } 351 | 352 | public record ClientRect(double x, double y, double width, double height); 353 | 354 | public async void HandleActivated(object? sender, ConfettiOptions options) 355 | { 356 | (double X, double Y)? originPosition = null; 357 | if (options.Origin is {} origin) 358 | { 359 | var helper = await JSRuntime.GetHelperAsync(); 360 | var jSInstance = await helper.InvokeAsync("getJSReference", origin); 361 | var client = await jSInstance.InvokeAsync("getBoundingClientRect"); 362 | originPosition = (X: client.x + client.width / 2, Y: client.y + client.height / 2); 363 | } 364 | 365 | var newPieces = Enumerable 366 | .Range(0, options.Pieces) 367 | .Select(i => 368 | new ConfettiPiece( 369 | options.Type, 370 | options.Colors[Random.Shared.Next(options.Colors.Length)], 371 | options.Size + (int)((Random.Shared.NextDouble() * 2 - 1) * options.SizeVariation), 372 | options.Milliseconds + (int)((Random.Shared.NextDouble() * 2 - 1) * options.VariationInMilliseconds), 373 | options.Mode switch 374 | { 375 | ConfettiOriginMode.FromBottomCenter => FromBottomCenterPositions(), 376 | ConfettiOriginMode.FromTopCurtain => FromTopCurtainPositions(), 377 | ConfettiOriginMode.FromElement when originPosition is not null => FromElementPositions(originPosition.Value), 378 | _ => FromBottomCenterPositions() 379 | } 380 | ) 381 | ).ToList(); 382 | 383 | lock (confettiPieces) 384 | { 385 | confettiPieces.AddRange(newPieces); 386 | } 387 | 388 | StateHasChanged(); 389 | await Task.Delay(options.Milliseconds + options.VariationInMilliseconds + 100); 390 | lock (confettiPieces) 391 | { 392 | confettiPieces = confettiPieces.Where(p => !newPieces.Contains(p)).ToList(); 393 | } 394 | StateHasChanged(); 395 | 396 | 397 | } 398 | 399 | private (double X, double Y)[] FromBottomCenterPositions() 400 | { 401 | var x = Random.Shared.NextDouble() - 0.5; 402 | var y = Random.Shared.NextDouble(); 403 | var end = Random.Shared.NextDouble(); 404 | return [(width / 2, height), (width / 2 + x * width / 3 * 2, height / 2 - y * height / 2), (width / 2 + x * width / 3 * 4, height + end * height / 3)]; 405 | } 406 | 407 | private (double X, double Y)[] FromTopCurtainPositions() 408 | { 409 | var x = Random.Shared.NextDouble() * width; 410 | return [(x, 0), (x, height)]; 411 | } 412 | 413 | private (double X, double Y)[] FromElementPositions((double x, double y) originPosition) 414 | { 415 | var x = Random.Shared.NextDouble() - 0.5; 416 | var y = Random.Shared.NextDouble(); 417 | var end = Random.Shared.NextDouble(); 418 | return [originPosition, (originPosition.x + x * width / 3 * 2, height / 2 - y * height / 2), (originPosition.x + x * width / 3 * 4, height + end * height / 3)]; 419 | } 420 | 421 | public void Dispose() 422 | { 423 | ConfettiService.Activated -= HandleActivated; 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiAnimator.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.SVGAnimation 2 | @using Microsoft.JSInterop 3 | 4 | 5 | @if (ConfettiPiece.Type is ConfettiType.SkewedRectangle) 6 | { 7 | 8 | } 9 | else if (ConfettiPiece.Type is ConfettiType.Heart) 10 | { 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | } 25 | else if (ConfettiPiece.Type is ConfettiType.HandsClapping) 26 | { 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | } 80 | else if (ConfettiPiece.Type is ConfettiType.Star) 81 | { 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | } 93 | 99 | 100 | 101 | @code { 102 | private ElementReference animateReference { get; set; } 103 | private SVGAnimationElement? animationElement; 104 | private bool visible = false; 105 | 106 | [Inject] 107 | public required IJSRuntime JSRuntime { get; set; } 108 | 109 | [Parameter, EditorRequired] 110 | public required ConfettiPiece ConfettiPiece { get; set; } 111 | 112 | protected override async Task OnAfterRenderAsync(bool firstRender) 113 | { 114 | if (!firstRender) return; 115 | 116 | try 117 | { 118 | animationElement = await SVGAnimationElement.CreateAsync(JSRuntime, animateReference); 119 | 120 | await animationElement.BeginElementAsync(); 121 | await animationElement.EndElementAtAsync(ConfettiPiece.Milliseconds); 122 | visible = true; 123 | StateHasChanged(); 124 | } 125 | catch (Exception e) 126 | { 127 | #if DEBUG 128 | Console.WriteLine(e); 129 | #endif 130 | } 131 | } 132 | 133 | private string SmoothPath((double X, double Y)[] points) 134 | { 135 | // Parameter for smoothness of path in interval [0, 0.5] 136 | double smoothness = 1.0 / 3.0; 137 | 138 | var result = ""; 139 | if (points.Length >= 2) 140 | { 141 | result = $"M {points[0].X.AsString()} {points[0].Y.AsString()} "; 142 | for (int i = 1; i < points.Length - 1; i++) 143 | { 144 | result += $"S {(points[i - 1].X * smoothness / 2 + points[i].X - points[i + 1].X * smoothness / 2).AsString()} {(points[i - 1].Y * smoothness / 2 + points[i].Y - points[i + 1].Y * smoothness / 2).AsString()} {points[i].X.AsString()} {points[i].Y.AsString()} "; 145 | } 146 | result += $"S {(points[^2].X * smoothness + points[^1].X * (1 - smoothness)).AsString()} {(points[^2].Y * smoothness + points[^1].Y * (1 - smoothness)).AsString()} {points[^1].X.AsString()} {points[^1].Y.AsString()} "; 147 | } 148 | return result; 149 | } 150 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace KristofferStrube.Blazor.Confetti; 4 | 5 | public class ConfettiOptions : EventArgs 6 | { 7 | /// 8 | /// The number of pieces of confetti to generate. The default is 300. 9 | /// 10 | public int Pieces { get; set; } = 300; 11 | 12 | /// 13 | /// The width of each confetti piece. The default is 10. 14 | /// 15 | public int Size { get; set; } = 10; 16 | 17 | /// 18 | /// The amount that the size can vary. The default is 10. 19 | /// 20 | public int SizeVariation { get; set; } = 1; 21 | 22 | /// 23 | /// The type of confetti there will be used. 24 | /// 25 | public ConfettiType Type { get; set; } = ConfettiType.SkewedRectangle; 26 | 27 | /// 28 | /// The time that the average confetti piece will be animated for. The default is 1000 milliseconds. 29 | /// 30 | public int Milliseconds { get; set; } = 1000; 31 | 32 | /// 33 | /// How much the animation time of each confetti piece can vary. 34 | /// 35 | public int VariationInMilliseconds { get; set; } = 200; 36 | 37 | /// 38 | /// The color of the confetti pieces. The default colors are "#F4F", "#44F", "#4F4", "#F44", and "#9F0". 39 | /// 40 | public string[] Colors { get; set; } = ["#F4F", "#44F", "#4F4", "#F44", "#9F0"]; 41 | 42 | /// 43 | /// How the confetti should be rendered. The default is 44 | /// 45 | public ConfettiOriginMode Mode { get; set; } = ConfettiOriginMode.FromBottomCenter; 46 | 47 | /// 48 | /// From where the animation should start if the value is chosen for the . 49 | /// 50 | public ElementReference? Origin { get; set; } 51 | } 52 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiOriginMode.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.Confetti; 2 | 3 | public enum ConfettiOriginMode 4 | { 5 | FromBottomCenter, 6 | FromTopCurtain, 7 | FromElement, 8 | } 9 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiPiece.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.Confetti; 2 | 3 | public record struct ConfettiPiece(ConfettiType Type, string Color, int Size, int Milliseconds, (double X, double Y)[] Positions); -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiService.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.Confetti; 2 | 3 | public class ConfettiService 4 | { 5 | public event ActivatedEventHandler? Activated; 6 | 7 | public void Activate(ConfettiOptions options) 8 | { 9 | Activated?.Invoke(this, options); 10 | } 11 | 12 | public delegate void ActivatedEventHandler(object sender, ConfettiOptions e); 13 | } 14 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/ConfettiType.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.Confetti; 2 | 3 | public enum ConfettiType 4 | { 5 | SkewedRectangle, 6 | Heart, 7 | Star, 8 | HandsClapping 9 | } 10 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/DoubleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace KristofferStrube.Blazor.Confetti; 4 | 5 | internal static class DoubleExtensions 6 | { 7 | internal static string AsString(this double d) 8 | { 9 | return d.ToString(CultureInfo.InvariantCulture); 10 | } 11 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/IServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace KristofferStrube.Blazor.Confetti; 4 | 5 | public static class IServiceExtensions 6 | { 7 | public static IServiceCollection AddConfettiService(this IServiceCollection serviceCollection) 8 | { 9 | return serviceCollection.AddScoped(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.Confetti/KristofferStrube.Blazor.Confetti.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Blazor Confetti 8 | Confetti component for Blazor. 9 | KristofferStrube.Blazor.Confetti 10 | Blazor;Confetti;SVG;Animation;SVGAnimation;JSInterop 11 | https://github.com/KristofferStrube/Blazor.Confetti 12 | git 13 | MIT 14 | 0.2.1 15 | Kristoffer Strube 16 | README.md 17 | icon.png 18 | true 19 | 20 | 21 | 22 | 23 | True 24 | \ 25 | 26 | 27 | True 28 | \ 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | --------------------------------------------------------------------------------