fastlane frame_screenshots(frameit)实战指南:用 Framefile.json 为 iOS/macOS/Android 截图批量套上设备边框
【免费下载链接】fastlane🚀 The easiest way to automate building and releasing your iOS and Android apps项目地址: https://gitcode.com/GitHub_Trending/fa/fastlane
本文围绕 fastlane 的frame_screenshots动作(其文档页即 frame_screenshots.md)展开。frameit让你只需一条命令,就能为 iOS、macOS 和 Android 截图批量套上精美的设备边框,并可叠加自定义背景、标题与关键词文字,用于 App Store、官网、QA 演示或邮件沟通。读完本文,你将掌握frameit的命令行与 Fastfile 用法、Framefile.json全部通用/特定/文字参数的含义与取值校验、macOS 截图的offset定位机制,以及--resume断点续帧、.strings本地化等实战技巧,并能结合仓库源码理解设备识别、边框下载与配置合并的底层实现。
一、frameit 能做什么
frame_screenshots是 fastlane 内置动作,作者为 KrauseFx,适用于 iOS、macOS、Android 三个平台(见 frame_screenshots.rb 的is_supported?定义)。它直接调用独立的 frameit 组件完成实际工作,frame_screenshots只是 thin wrapper:进入截图目录后调用Frameit::Runner.new.run('.')(见 frame_screenshots.rb)。
核心能力:
- 设备边框:支持 iPhone、iPad、Mac 与一组 Android 设备,覆盖竖屏(Portrait)与横屏(Landscape),并提供多种边框颜色;
- 高级能力:为带边框截图添加自定义彩色/图片背景、设置四周留白(padding)、在边框上方或下方添加「关键词 + 标题」双行文字,可自定义字体、字号、颜色、字重,支持多行文本,并带有"智能"文字排版;
- 多平台:iOS/Mac 为默认平台(向后兼容),Android 需显式指定。
首次运行frameit时,设备边框模板会自动下载到本地。边框素材最初来自 Facebook Design 的设备资源,并托管在 fastlane 的 frameit-frames 仓库;下载器会在输出中打印一段关于素材授权与品牌使用规范的免责声明(见 frame_downloader.rb)。更完整的受支持设备与颜色列表请以 frameit-frames 仓库的latest版本为准。
边框下载与缓存机制
从源码可以看到 FrameDownloader 的行为:
- 边框统一缓存在
~/.fastlane/frameit/<版本>/目录(若存在旧路径~/.frameit/devices_frames_2/则沿用),frames_version默认是latest,可在Framefile.json的device_frame_version字段锁定具体版本; - 下载流程会拉取
version.txt与files.json,逐个写入 PNG 边框和offsets.json,并刻意把version.txt放在最后写入,以便中断后能断点续传(见 frame_downloader.rb); - 版本判定逻辑在 module.rb:扫描当前目录下
**/Framefile.json,读取其中的device_frame_version。
注意:不带标题(无背景)的
frameit输出是全分辨率图片,不能直接上传 App Store,更适合网站、印刷和邮件场景;要为 App Store 准备截图,请配合下方的Framefile.json标题配置。
二、基本用法
命令行方式
先cd到截图目录,然后执行(iOS 与 macOS 是默认平台):
fastlane frameit为 Android 截图套框:
fastlane frameit android使用银色(白色系)边框:
fastlane frameit silver手动(重新)下载最新边框:
fastlane frameit download_framesFastfile 方式
frame_screenshots与frameit是同一动作的两个名字,仓库声明的示例写法如下(见 frame_screenshots.rb 的example_code):
frame_screenshots frameit # alias for "frame_screenshots" frame_screenshots(use_platform: "ANDROID") frame_screenshots(silver: true) frame_screenshots(path: "/screenshots") frame_screenshots(rose_gold: true)主要选项及其环境变量定义在 options.rb 中,摘录如下:
| 选项 | 环境变量 | 说明 | 默认值 |
|---|---|---|---|
path | FRAMEIT_SCREENSHOTS_PATH | 截图目录路径;默认取snapshot输出的SNAPSHOT_SCREENSHOTS_PATH,否则为fastlane目录 | 动态 |
white/silver | FRAMEIT_WHITE_FRAME/FRAMEIT_SILVER_FRAME | 使用白色系边框(silver是white的别名) | 否 |
rose_gold | FRAMEIT_ROSE_GOLD_FRAME | 玫瑰金边框 | 否 |
gold | FRAMEIT_GOLD_FRAME | 金色边框 | 否 |
force_device_type | FRAMEIT_FORCE_DEVICE_TYPE | 强制指定设备型号(对 Mac 截图尤其有用,因为其尺寸多变),取值必须是受支持的设备名 | 无 |
use_platform | FRAMEIT_USE_PLATFORM | 指定平台,合法值IOS、ANDROID、ANY;默认为 Fastfile 当前平台或IOS | 动态 |
force_orientation_block | — | [高级] 一个 proc,根据文件名强制指定截图方向 | 见下文 |
debug_mode | FRAMEIT_DEBUG_MODE | 在输出图中打印调试信息 | false |
resume | FRAMEIT_RESUME | 增量续帧,跳过已是最新状态的截图 | false |
use_legacy_iphone5s等 | FRAMEIT_USE_LEGACY_IPHONE_5_S等 | 一组布尔开关:在 iPhone SE/7/8/XS/XR 与旧机型(5s/6s/7/X/XR/XS/XS Max)边框之间切换 | false |
运行器对文件还有一套过滤规则(见 runner.rb):_framed.png、.itmsp/包文件、device_frames/目录会被跳过,Apple Watch 截图会报错提示并跳过。输出文件统一命名为<原文件名>_framed.png(见 screenshot.rb)。
三、Framefile.json:通用与特定参数
Framefile.json应放在截图根目录(即screenshots文件夹)下,用于定义全局与单张截图级的信息。注意 frameit 会在截图文件的上一级、上两级、以及../../../../相对位置逐级寻找Framefile.json(见 runner.rb 的create_config,这一行为是为了兼容 screengrab 的多层目录结构)。
基本 JSON 结构
{ "device_frame_version": "latest", "default": { ... }, "data": [ ... ] }device_frame_version:锁定边框版本,缺省为latest;default:全局默认参数;data:数组,每个元素通过filter匹配文件名,实现单图覆盖。
通用参数(default键)
| 键 | 说明 | 默认值 |
|---|---|---|
background | 带边框截图使用的背景图,写(相对)路径(如*.jpg)。带标题时此参数必填 | NA |
keyword | 可选关键词对象,最多 3 个键,见文字参数表 | NA |
title | 标题对象,最多 3 个键,见文字参数表 | NA |
stack_title | 同时定义 keyword 与 title 时,是否让 keyword 叠放在 title 上方;为false时两者并排显示 | false |
title_below_image | 是否把标题(及可选关键词)放到设备边框下方;为false时放在上方 | false |
show_complete_frame | 是否缩小设备边框使其完整显示;为false时边框底部(或title_below_image为true时的顶部)可能被裁切 | false |
padding | 内容四周留白。三种写法:1) 整数,同时指定水平/垂直像素;2) 字符串"水平x垂直"像素,如"30x60";3) 字符串百分比,如"5%x10%"(百分比按最小边计算)。可混合如"5%x40"。垂直 padding 同时作用于文字与边框上/下边缘 | 50 |
interline_spacing | 多行 title/keyword 行距增/减的像素值 | 0 |
font_scale_factor | 字体缩放系数;若显式指定font_size则此值对 keyword/title 无效 | 0.1 |
frame | 覆盖边框颜色,合法值BLACK、WHITE、GOLD、ROSE_GOLD | NA |
title_min_height | 为标题始终预留的高度,可为高度百分比或绝对像素值,保证不同截图中设备顶部(或底部)位置一致 | NA |
use_platform | 覆盖截图平台,合法值IOS、ANDROID、ANY | IOS |
force_device_type | 强制指定设备。原文档列出的合法值包括:Huawei P8、Motorola Moto E、Motorola Moto G、Nexus 4、Nexus 5X、Nexus 6P、Nexus 9、Samsung Galaxy Grand Prime、Samsung Galaxy Note 5、Samsung Galaxy S Duos、Samsung Galaxy S3、Samsung Galaxy S5、S7、S8、S9、iPhone 5s、5c、SE、6s、6s Plus、7、7 Plus、8、8 Plus、X、XS、XR、XS Max、iPad Air 2、iPad Mini 4、iPad Pro、MacBook、Google Pixel 3、Pixel 3 XL、HTC One A9、HTC One M8(具体设备表以源码 device_types.rb 为准,可能随版本演进) | NA |
这些键在解析阶段会被逐一校验:font/background路径必须真实存在、color必须含#的 HEX 值、padding必须是整数或AxB格式、show_complete_frame/title_below_image必须是布尔值、frame/use_platform/force_device_type必须属于合法枚举,否则直接报错退出(见 config_parser.rb 的validate_key)。另外,font与background的相对路径会自动改写为相对于Framefile.json所在目录的绝对路径(见 config_parser.rb)。
特定参数(data键)
data是数组,每个元素通过filter与文件名部分匹配,只把该元素内的键应用于匹配的截图:
| 键 | 说明 |
|---|---|
filter | 必填,用文件名的一部分关联配置。例如截图名为iPhone 8-Brainstorming.png时可用Brainstorming。若多个filter同时命中,将按顺序全部应用(最后一个优先级最高) |
keyword | 同default中的keyword,但这里可以直接写text(因为文案是单图特定的) |
title | 同default中的title,但这里可以直接写text |
frame | 覆盖边框颜色(BLACK、WHITE、GOLD、ROSE_GOLD) |
use_platform | 覆盖平台(IOS、ANDROID、ANY) |
force_device_type | 强制指定设备,合法值同通用参数 |
从源码看,合并逻辑是:先克隆default,再把所有命中的data元素用深度合并(fastlane_deep_merge,定义在 module.rb)依次叠加上去(见 config_parser.rb 的fetch_value),因此特定参数天然覆盖默认参数。
Framefile 中 keyword 与 title 参数
keyword与title在default与data中均可使用,二者都由以下可选键组成:
| 键 | 说明 | 默认值 |
|---|---|---|
color | 文字字体颜色,HEX/HTML 颜色码 | #000000(黑) |
font | 文字字体族,写(相对)字体文件路径(如 OpenType 字体) | imagemagick默认字体(依赖系统) |
font_size | 字号(点)。未指定或为0时按可用空间自动缩放;即使如此,文字放不下时 frameit 仍会缩小 | NA |
font_weight | 字重,整数(如 900),遵循 ImageMagick 的 weight 语义 | NA |
text | keyword 或 title 的文本内容。如需本地化,改用下方.strings 文件 | NA |
完整示例
{ "device_frame_version": "latest", "default": { "keyword": { "font": "./fonts/MyFont-Rg.otf" }, "title": { "font": "./fonts/MyFont-Th.otf", "font_size": 128, "color": "#545454" }, "background": "./background.jpg", "padding": 50, "show_complete_frame": false, "stack_title": false, "title_below_image": true, "frame": "WHITE", "use_platform": "IOS" }, "data": [ { "filter": "Brainstorming", "keyword": { "color": "#d21559" } }, { "filter": "Organizing", "keyword": { "color": "#feb909" }, "frame": "ROSE_GOLD" }, { "filter": "Sharing", "keyword": { "color": "#aa4dbc" } }, { "filter": "Styling", "keyword": { "color": "#31bb48" } }, { "filter": "Android", "use_platform": "ANDROID" } ] }参数优先级(源码视角)
screenshot.rb 的注释明确了三套配置的来源与优先级(从低到高):
- options.rb:Fastfile 选项或环境变量(如
FRAMEIT_USE_PLATFORM),含默认值与校验,最低优先级; - 命令行位置参数:如
fastlane frameit android,经 commands_generator 传入,可覆盖第 1 层; - Framefile.json:
default与data(按文件名过滤),最高优先级——用户为某张截图设的特定值应覆盖 CLI 与 Fastfile 全局设置。
对应到代码即platform = config['use_platform'] || platform_command || Frameit.config[:use_platform](screenshot.rb),设备检测同理:force_device_type优先,否则由Device.detect_device(path, platform)按分辨率自动识别(screenshot.rb)。
四、截图方向控制
默认情况下 frameit 按截图本身的方向加边框:竖屏图用竖屏边框,横屏图用「landscape left」(Home 键在左侧)边框。源码中默认 block 还识别文件名后缀:以force_landscapeleft或force_landscaperight结尾的文件名会强制对应横屏方向(见 options.rb);screenshot.rb 的frame_orientation则先调用该 block,未返回有效值时回落到「宽<高即竖屏,否则 landscape_right」。
force_orientation_block
如果默认行为不满足需求、又不想重命名截图,可以在 Fastfile 中传入force_orientation_blockproc,按文件名返回:landscape_left(Home 键在左)、:landscape_right(Home 键在右)、:portrait(Home 键在下)或nil(回退默认)。
# 根据文件名匹配对应的设备方向 frameit( path: "./fastlane/screenshots", force_orientation_block: proc do |filename| case filename when "iPad Pro (12.9-inch)-01LoginScreen" :landscape_right when "iPhone 6 Plus-01LoginScreen" :portrait # and so on end end )# 文件名包含 landscape 字样时,一律横屏(Home 键在右) frameit( silver: true, path: "./fastlane/screenshots", force_orientation_block: proc do |filename| f = filename.downcase if f.include?("landscape") :landscape_right end end )注意:block 返回nil之外的非法符号会直接报错(orientation_block must return :landscape_left, :landscape_right, :portrait or nil,见 screenshot.rb)。
五、macOS 截图
frameit 同样可以为 macOS 应用截图套框,但需要额外提供:
background:一张同时包含背景与 Mac 电脑的图片(相对路径);offset信息,告诉 frameit 把截图贴到哪里:offset:字符串,相对背景图左上角的水平/垂直像素偏移,语法"+<水平>+<垂直>",例如"+200+150";titleHeight:标题占用的像素高度。
{ "default": { "title": { "color": "#545454" }, "background": "Mac.jpg", "offset": { "offset": "+676+479", "titleHeight": 320 } }, "data": [ { "filter": "Brainstorming", "keyword": { "color": "#d21559" } } ] }源码中,runner.rb 的editor方法会判断screenshot.mac?(即设备名为MacBook,见 screenshot.rb),是则走MacEditor,否则走普通Editor,两条渲染路径分开实现。
六、.strings 文件本地化
要为多个语言目录提供标题与关键词,可在每个语言文件夹(如en-US)中放入两个.strings文件:keyword.strings与title.strings。它们就是 iOS 应用中标准.strings文件,可以直接复用现有翻译服务产出,实现标题本地化。仓库内可参考的 fixture 见 title-same-dir.strings 与 Framefile.json。
注意事项(务必满足,否则解析失败):
.strings文件必须是 UTF-8 或 UTF-16 BE(带 BOM)编码,且必须以空行开头;- 想显示标题就必须提供
background,没有背景时 frameit 不会添加标题。
解析实现在 strings_parser.rb:先用file --mime-encoding探测编码,UTF-8/ASCII 直接读取,其他编码则调用iconv -f UTF-16 -t UTF-8转换,然后逐行解析"key" = "value";形式的条目,跳过注释与空行;解析结果为空时会提示检查文件是否为合法的 UTF-16 Big-endian 编码。
七、实战技巧(Tips)
生成本地化截图
配合 fastlane 的snapshot动作用 UI Automation 自动生成各语言截图,再交给 frameit 套框,形成完整流水线。
--resume 断点续帧
套框是耗时操作。需要恢复中断的批次、或只对少数更新的截图重新套框时,使用--resume标志(Fastfile 中为resume: true,环境变量FRAMEIT_RESUME)。只有尚未生成、或没有"最新"带框图片的截图才会被处理。它基于文件修改时间判断:若原截图比_framed.png新,则重新套框(见 screenshot.rb 的outdated?与 runner.rb 的skip_up_to_date?)。
上传截图
生成后可用 fastlane 的deliver动作上传 iOS 截图到 App Store Connect,用supply动作上传 Android 截图到 Play Store,实现全自动发布。
使用干净的 Status Bar
在snapshot中设置override_status_bar: true,可把模拟器状态栏固定为 1 月 9 日(周二)9:41、满电满信号;需要更细粒度定制(例如运营商名称)时,再设置override_status_bar_arguments,其值会透传给xcrun simctl status_bar override,可用xcrun simctl status_bar --help查看可选项。
# 9:41AM,满电满信号,默认运营商名 Carrier capture_ios_screenshots( override_status_bar: true )# 9:41AM,电量 75% 且充电中,TELUS LTE 网络 capture_ios_screenshots( override_status_bar: true, override_status_bar_arguments: "--time 9:41 --dataNetwork lte --cellularMode active --cellularBars 4 --batteryState charging --batteryLevel 75 --operatorName TELUS" )文字周围出现灰色杂边
如果渲染出现字体边缘描边等质量问题,通常重装imagemagick即可解决:
brew uninstall imagemagick brew install imagemagickframeit 的文字渲染底层依赖 ImageMagick 命令行能力,这也是font参数未指定时回落到"系统默认 imagemagick 字体"的原因。
八、卸载
- gem uninstall fastlane - rm -rf ~/.frameit第二行清理旧版边框缓存目录;当前版本默认缓存已迁移至~/.fastlane/frameit/(见 frame_downloader.rb),如需彻底清理可一并删除。
小结
frame_screenshots(frameit)把"给截图套设备边框"这件原本要靠 Photoshop 手工完成的工作收敛为一条命令:命令行适合简单场景(选平台、选颜色),Framefile.json负责精细控制(背景、padding、标题/关键词、逐图覆盖),force_orientation_block处理特殊方向,macOS 场景补充offset定位,.strings文件则让标题随语言目录自动本地化。结合源码可以看到,其核心是"配置三层优先级 + 分辨率自动识别设备 + 断点续帧"的组合:理解 ConfigParser 的深度合并与校验、Screenshot 的方向判定、Runner 的跳过规则后,遇到"某张图没被套框""某参数不生效"之类问题时可以快速定位原因。相关规格测试位于 frameit/spec,例如 editor_spec.rb、config_parser_spec.rb 与 template_finder_spec.rb,可作为行为验证的参考。
【免费下载链接】fastlane🚀 The easiest way to automate building and releasing your iOS and Android apps项目地址: https://gitcode.com/GitHub_Trending/fa/fastlane
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考