examples/07-Animations.ps1
|
# 07 - Animations: a live dashboard with CSS keyframes, the Web Animations API and a canvas loop. # # Windows Server 2025 (and any Windows with "Animation effects" turned off) tells web pages # "prefers-reduced-motion: reduce" (Server 2025 has them off by default). That is only a REQUEST: CSS keyframes, # the Web Animations API and requestAnimationFrame keep running. This page ignores the setting simply by never # asking for it - it has no @media (prefers-reduced-motion) rule and no matchMedia check that stops motion. # (Measured 2026-11-12 on Windows Server 2025, WebView2 157: animation effects off, the page sees "reduce", # all four endless animations run and the canvas draws at ~30 fps.) Avoid CSS or libraries that honour the # setting if motion must always play. The page reports what really ran; it is printed when the window closes. if (-not (Get-Command Show-NativeWebView -ErrorAction Ignore)) { Import-Module NativeWebView -ErrorAction Stop } $Html = @' <!doctype html> <html><head><style> :root { --navy: #154363; --blue: #095495; --sky: #a2bfd6; --text: #454545; --green: #4F8035; } * { box-sizing: border-box; } body { margin: 0; font-family: Verdana, 'Segoe UI', Arial, sans-serif; font-size: 13px; color: var(--text); background: #F9F9F9; overflow: hidden; } header { padding: 18px 24px; color: white; background: linear-gradient(120deg, var(--navy), var(--blue), var(--sky), var(--blue), var(--navy)); background-size: 300% 100%; animation: sweep 8s linear infinite; } header h1 { margin: 0; font-size: 20px; letter-spacing: 1px; } header p { margin: 4px 0 0; opacity: .85; } @keyframes sweep { from { background-position: 0% 0; } to { background-position: 300% 0; } } .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding: 18px 24px 0; } .card { background: white; border: 1px solid #CCCCCC; border-radius: 8px; padding: 14px; opacity: 0; animation: rise .7s cubic-bezier(.2,.8,.2,1) forwards; } .card:nth-child(1) { animation-delay: .1s; } .card:nth-child(2) { animation-delay: .25s; } .card:nth-child(3) { animation-delay: .4s; } @keyframes rise { from { opacity: 0; transform: translateY(24px) scale(.96); } to { opacity: 1; transform: none; } } .card h3 { margin: 0 0 8px; color: var(--navy); font-size: 13px; } .big { font-size: 28px; font-weight: bold; color: var(--blue); font-variant-numeric: tabular-nums; } .ring { width: 56px; height: 56px; border-radius: 50%; border: 6px solid #e4e4e4; border-top-color: var(--blue); animation: spin 1s linear infinite; margin: 4px auto; } @keyframes spin { to { transform: rotate(360deg); } } .pulse { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--green); margin-right: 6px; animation: pulse 1.4s ease-in-out infinite; } @keyframes pulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.8); opacity: .3; } } .bar { height: 12px; border-radius: 6px; background: #e4e4e4; overflow: hidden; margin-top: 10px; } .fill { height: 100%; width: 100%; transform-origin: left; background: linear-gradient(90deg, var(--blue), var(--sky)); } canvas { display: block; margin: 14px 24px 0; width: calc(100% - 48px); height: 120px; background: white; border: 1px solid #CCCCCC; border-radius: 8px; } footer { display: flex; justify-content: space-between; align-items: center; padding: 12px 24px; } #status { color: var(--green); } button { padding: 8px 18px; background: var(--blue); color: white; border: 0; border-radius: 4px; font: inherit; cursor: pointer; transition: background .2s, transform .1s; } button:hover { background: var(--navy); } button:active { transform: scale(.96); } </style></head> <body> <header><h1>NativeWebView live dashboard</h1><p>CSS keyframes, Web Animations and a canvas loop - from PowerShell</p></header> <div class="grid"> <div class="card"><h3>Requests</h3><div class="big" id="count">0</div><div class="bar"><div class="fill" id="fill"></div></div></div> <div class="card"><h3>Working</h3><div class="ring"></div></div> <div class="card"><h3>Status</h3><p><span class="pulse"></span>Online</p><p id="clock">-</p></div> </div> <canvas id="wave" width="900" height="240"></canvas> <footer><span id="status">Measuring animations...</span><button id="close">Close</button></footer> <script> // Web Animations API: the progress bar fills and empties forever. document.getElementById("fill").animate( [{ transform: "scaleX(0)" }, { transform: "scaleX(1)" }, { transform: "scaleX(0)" }], { duration: 3000, iterations: Infinity, easing: "ease-in-out" }); // requestAnimationFrame: a counter and a two-colour wave on a canvas. var canvas = document.getElementById("wave"), g = canvas.getContext("2d"), frames = 0, start = performance.now(); function draw(now) { frames++; var t = (now - start) / 1000, w = canvas.width, h = canvas.height; g.clearRect(0, 0, w, h); [["#095495", 1, 0], ["#a2bfd6", 0.6, 1.3]].forEach(function (s) { g.beginPath(); g.lineWidth = 4; g.strokeStyle = s[0]; for (var x = 0; x <= w; x += 6) { var y = h / 2 + Math.sin(x / 60 + t * 2 + s[2]) * h * 0.3 * s[1] * Math.sin(t * 0.7 + x / 300); if (x === 0) { g.moveTo(x, y); } else { g.lineTo(x, y); } } g.stroke(); }); document.getElementById("count").textContent = Math.floor(t * 37).toLocaleString(); document.getElementById("clock").textContent = new Date().toLocaleTimeString(); requestAnimationFrame(draw); } requestAnimationFrame(draw); // After 2 s: measure what really moved and tell PowerShell. setTimeout(function () { var css = document.getAnimations().filter(function (a) { return a.playState === "running" && a.currentTime > 0; }).length; var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; var fps = Math.round(frames / ((performance.now() - start) / 1000)); document.getElementById("status").textContent = "Running: " + css + " animations, " + fps + " fps" + (reduced ? " (system asks for reduced motion - ignored)" : ""); kHost.send({ action: "report", running: css, fps: fps, reducedMotion: reduced, agent: navigator.userAgent }); }, 2000); document.getElementById("close").onclick = function () { kHost.send({ action: "close" }); }; </script> </body></html> '@ $Report = @{} Show-NativeWebView -Html $Html -Title "Live dashboard" -Width 760 -Height 520 -OnMessage { param($Message, $Window) $Data = $Message | ConvertFrom-Json if ($Data.action -eq "report") { $Report.Running = $Data.running; $Report.Fps = $Data.fps; $Report.Reduced = $Data.reducedMotion; $Report.Agent = $Data.agent } if ($Data.action -eq "close") { $Window.Close() } }.GetNewClosure() if ($Report.Count -gt 0) { Write-Host ("Animations running: " + $Report.Running + ", " + $Report.Fps + " fps, page saw reduced motion: " + $Report.Reduced) } |