关于 screen-recording
Screen Recording 是用于制作 UI 交互、桌面应用和工作流动画 GIF 演示的技能。它结合画面捕获、时长控制与注释,适用于文档、PR 和版本说明。
主要功能
- 01使用 Playwright 捕获浏览器画面
- 02使用 mss 录制桌面区域
- 03使用 imageio 组装 GIF
- 04设置可变帧时长
- 05添加注释及淡入效果
适用场景
- 01在 PR 中展示多步 UI 流程
- 02记录行为变化前后对比
- 03演示错误复现或修复过程
- 04制作带注释的版本发布演示
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill screen-recording安装后在 Claude Code 中输入/screen-recording确认已加载。
详细介绍
屏幕录制
创建动态 GIF 演示,展示某项功能或工作流的实际运行效果——配有标注、可变时长和恰当的节奏。适用于 PR 描述、文档和发布说明。
何时使用本技能
当你需要以下操作时,可使用本技能:
- 将多步骤 UI 交互录制为动态 GIF
- 创建展示前后行为对比的演示
- 为文档或发布说明构建带标注的逐步讲解
- 展示缺陷复现或修复的实际过程
前置条件
pip install playwright Pillow imageio numpy scipy mss -q
playwright install chromium
核心工作流
1. 捕获帧
使用 Playwright 逐步执行交互并捕获每一帧:
from playwright.async_api import async_playwright
async def record_frames(url, steps, width=1400, height=900):
"""
steps: list of dicts with 'action' (async callable taking page)
and 'name' (frame filename)
"""
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width": width, "height": height})
await page.goto(url, wait_until="networkidle")
for step in steps:
if step.get("action"):
await step["action"](page)
await page.wait_for_timeout(step.get("wait", 500))
await page.screenshot(path=step["name"])
await browser.close()
2. 用 imageio 合成 GIF
写入 GIF 时请使用 imageio,而不是 PIL——PIL 的 GIF 编码器会合并视觉上相似的帧,从而毁掉动画。
import imageio.v3 as iio
from PIL import Image
import numpy as np
frames = []
durations = []
for frame_path, duration_ms in frame_list:
img = Image.open(frame_path)
frames.append(np.array(img))
durations.append(duration_ms)
iio.imwrite("demo.gif", frames, duration=durations, loop=0)
3. 可变帧时长
统一的时长会让一切要么显得太快,要么显得太慢。请使用可变时长:
| 阶段 | 时长 | 原因 |
|---|---|---|
| 快速动作(输入、点击) | 100ms | 感觉自然,保持节奏感 |
| 动作后的停顿 | 600-800ms | 让观看者消化刚刚发生的事 |
| 主视觉/最终信息 | 500ms+ | 核心要点需要时间被理解 |
4. 为帧添加标注
使用 image-annotations 技能将标注应用到特定帧:
from PIL import Image, ImageDraw, ImageFont
def annotate_frame(frame_path, annotations, out_path):
img = Image.open(frame_path)
draw = ImageDraw.Draw(img)
for ann in annotations:
# Apply annotation (rect, arrow, label, etc.)
pass
img.save(out_path)
5. 淡入标注
实现标注的平滑出现效果:
def apply_fade(base_frame, annotation_layer, alpha):
"""Blend annotation onto frame at given alpha (0.0 to 1.0)"""
blended = Image.blend(
base_frame.convert("RGBA"),
annotation_layer.convert("RGBA"),
alpha
)
return blended.convert("RGB")
# 2-frame pop-in at 10fps: 50% then 100%
faded_frames = [
apply_fade(base, annotations, 0.5), # frame 1: half opacity
apply_fade(base, annotations, 1.0), # frame 2: full opacity
]
在 10fps 下,使用 2 个淡入帧(共 0.2s)。在 30fps 下,使用 3-4 帧。缓动曲线在低 FPS 下效果不佳——简单的弹出式出现更利落,也更易读。
以脚本形式构建
一旦超出简单演示的范围,标注逻辑就会变得复杂。请编写一个专用脚本(例如 annotate_gif.py),用函数组织代码,而不是内联编写。你会反复调整时长和位置。
测试动画
务必先单独测试——不要为了测试一处淡入调整而重新构建整个演示:
# Small test GIF: 10 bare frames → fade frames → 15 hold frames
# Add a frame counter overlay for debugging:
draw.text((10, height - 30), f"F{i}/{total} a={alpha:.0%} FADE",
fill="white", font=small_font)
桌面屏幕录制(mss)
用于录制桌面应用、终端或浏览器之外的任何内容。使用 mss 进行快速屏幕捕获。
import mss
from PIL import Image
import time
def record_gif(output_path, region=None, duration=5, fps=8):
"""Record screen region to GIF. region = {left, top, width, height} or None for full screen."""
with mss.mss() as sct:
if region is None:
region = sct.monitors[1] # primary monitor
frames = []
t_end = time.time() + duration
while time.time() < t_end:
t0 = time.time()
shot = sct.grab(region)
frames.append(Image.frombytes('RGB', shot.size, shot.rgb))
time.sleep(max(0, 1 / fps - (time.time() - t0)))
frames[0].save(output_path, save_all=True, append_images=frames[1:],
duration=int(1000 / fps), loop=0, optimize=True)
return len(frames)
record_gif('demo.gif', region={'left': 0, 'top': 0, 'width': 800, 'height': 500}, duration=3)
已测试:3s 时长、8fps → 24 帧,约 31KB。将 fps 保持在 ≤ 10 以获得合理的文件大小。
注意: PIL.save(save_all=True) 适用于简单录制,但会合并视觉上相似的帧。对于带淡入效果的标注 GIF,请改用 imageio.v3.imwrite。
结合窗口捕获
# Find window rect, then record it as a GIF
# Reuse find_window() from the ui-screenshots skill
import ctypes
from ctypes import c_int, Structure, byref, windll
class RECT(Structure):
_fields_ = [('left', c_int), ('top', c_int), ('right', c_int), ('bottom', c_int)]
hwnd = find_window('My App')[0][0]
rect = RECT()
windll.user32.GetWindowRect(hwnd, byref(rect))
region = {'left': rect.left, 'top': rect.top,
'width': rect.right - rect.left, 'height': rect.bottom - rect.top}
record_gif('app-demo.gif', region=region, duration=5, fps=8)
基于差异的聚类检测
以编程方式找出帧之间发生变化的区域,从而决定要标注什么:
import numpy as np
from scipy import ndimage
def find_changed_clusters(frame_a, frame_b, threshold=30, min_pixels=300, dilate=5):
"""Find bounding boxes of changed regions between two frames."""
diff = np.abs(frame_b.astype(float) - frame_a.astype(float)).max(axis=2)
mask = diff > threshold
dilated = ndimage.binary_dilation(mask, iterations=dilate)
labeled, n = ndimage.label(dilated)
clusters = []
for i in range(1, n + 1):
ys, xs = np.where(labeled == i)
if len(ys) < min_pixels:
continue
clusters.append((xs.min(), ys.min(), xs.max(), ys.max(), len(ys)))
return sorted(clusters, key=lambda c: -c[4]) # largest first
格式兼容性
| 格式 | VS Code 预览 | GitHub | 浏览器 |
|---|---|---|---|
| GIF | ✅ 可动画 | ✅ | ✅ |
| WebP | ⚠️ 仅静态 | ✅ | ✅ |
| MP4 | ❌ 无法显示 | ⚠️ | ✅ |
GIF 是唯一在 VS Code 预览、GitHub markdown 和浏览器中普遍支持动态效果的格式。
指南
- 输入 → 停顿 → 标注——快速动作期间,不显示任何标注。先停顿,再标注
- 主视觉信息使用最大字号——核心要点用 64pt+,细节用 38pt
- GIF 调色板不会毁掉渐变——256 色调色板可保留 20 个不同的 alpha 层级
- 输入/交互最低 10fps——更低会显得卡顿
- 迭代式构建——先把帧序列做对,其次添加标注,最后调整时长
限制
- GIF 每帧仅限 256 色——对于 UI 截图足够,但在照片类内容上可能出现色带
- 大型 GIF(高分辨率下 50+ 帧)可能达到数 MB——考虑裁剪到相关区域
- GIF 不支持音频——旁白演示请使用 MP4(但会失去 VS Code 预览支持)