简介:一款让微信用户实现类QQ“闪照”体验的小程序源码,面向微信小程序开发者、运营者以及喜欢尝试新鲜社交玩法的个人用户。基于微信小程序原生组件与模块化目录构建,支持用户自主上传照片并像QQ一样自由设定闪照可见时长,既可用于好友间的趣味互动,也能作为一个轻量工具类小程序接入流量主广告位,带来一定收益。压缩包共29个文件,总体积仅166KB,其中WXML负责页面布局、WXSS负责样式表现、JS与JSON负责交互逻辑和全局配置,另附说明文档与合法域名配置指南,页面、组件、静态资源等目录划分清晰,整体采用原生语法、不依赖重型框架,阅读与二次开发门槛较低。已有315人学习浏览,尤其适合想通过一个完整案例掌握小程序上传、配置与审核全流程的入门者,也可作为功能完整且易定制的社交模板直接改造成自己的作品。
1. 微信小程序里的“阅后即焚”:闪照源码到底值不值得折腾
打开微信对话框,你发现对方发来一张图片——点开是模糊的,手指长按会提示“闪照不可保存”,过几秒图片自动销毁。这是QQ闪照的核心交互逻辑,而现在一批小程序开发者正在把这套玩法搬进微信里。这款闪照制作小程序源码,做的就是这件事:用户上传本地照片,自定义闪照的可视时长(比如3秒、5秒、10秒),接收方看到的是带倒计时的图片,时间一到图片从屏幕和缓存里一起消失。它还内置了流量主广告位,可以在闪照浏览结束后插入激励视频或Banner,把“装逼工具”变成可变现的流量入口。适合三类人:想在微信生态里做轻社交玩法的产品经理、刚学完小程序基础想拆一个完整工程的开发者、以及手里有流量想快速上一款工具类小程序试水的运营者。下文不聊概念,直接拆源码结构和能跑的细节。
2. 源码目录拆解:从 colorui 到 app.json 的工程骨架
2.1 根目录文件:先看清这个工程跑起来需要哪些前置条件
解压闪照.zip之后,第一眼看到的不是pages,而是app.json、app.js、app.wxss以及colorui这个目录。colorui是一套开源的高颜值微信小程序 UI 组件库,优势在于不需要 npm 安装,直接把colorui文件夹丢进项目根目录,在app.wxss里@import样式文件即可。这个工程选择 colorui 而不是 WeUI 或 Vant Weapp,图的是两件事:一是组件视觉更接近社交类产品的圆润风格,二是省掉了 npm build 这一步——在微信开发者工具里直接“导入项目”就能看到界面,迭代速度明显更快。
app.json是整个小程序的全局配置文件,路由、窗口样式、tabBar 都在这里声明。打开它看到的关键配置如下:
{ "pages": [ "pages/index/index", "pages/create/create", "pages/preview/preview" ], "window": { "navigationBarBackgroundColor": "#1a1a1a", "navigationBarTitleText": "闪照", "navigationBarTextStyle": "white" }, "sitemapLocation": "sitemap.json" }pages数组的第一项就是小程序的启动页,这里把pages/index/index作为首页,意味着用户打开小程序先看到的是闪照列表或功能入口。window里的黑色导航栏配合闪照这种“阅后即焚”的暗色交互,视觉上更聚焦照片本身。sitemap.json用于配置微信索引小程序的页面收录规则,工具类小程序一般保持默认。
2.2 pages、components、common、static 四层职责划分
这个源码的四层目录划分逻辑很清晰:pages放页面级代码,components放可复用组件,common放公共工具函数和配置常量,static放静态资源(图片、图标、占位图)。闪照这种功能型小程序,核心页面不超过四个,功能集中在“创建闪照”和“查看闪照”两条主链路上,所以目录层级比电商类小程序扁平得多,新手读起来没有负担。
common目录里值得单独看的是config.js和utils.js两个文件。config.js维护小程序的基础配置——AppID、接口域名、是否启用流量主广告位、默认闪照时长等。我自己接手这类源码的第一步,永远是先打开config.js把配置项过一遍,因为大部分“上传后白屏”“广告不显示”的 bug 根源都出在这里。utils.js则封装了格式化时间戳、图片本地路径拼接、节流函数这类通用工具方法。
2.3 闪照时间配置的数据流:从用户输入到页面读取
闪照的核心卖点是“自定义时间”,这个值在源码里是通过一条单向数据流来管理的:用户在创建页选择时间 → 存入全局storage→ 预览页读取并启动定时器。具体存储结构类似:
// common/config.js const FLASH_CONFIG = { DEFAULT_SECONDS: 5, MAX_SECONDS: 30, STORAGE_KEY: 'flash_photo_item' }; function saveFlashItem(item) { wx.setStorageSync(FLASH_CONFIG.STORAGE_KEY, { ...item, expireAt: Date.now() + item.seconds * 1000 }); }saveFlashItem在写入瞬间就算好了expireAt时间戳,而不是在读取端临时计算。这样做的好处是,即使小程序被微信从后台回收、重新冷启动打开,也能通过Date.now()与expireAt的差值判断这张闪照是否已经过期——这个细节决定了“销毁”到底是真的销毁,还是只是前端视觉上隐藏。
| 目录/文件 | 职责 | 关键内容 |
|---|---|---|
app.json | 全局路由与窗口配置 | 页面注册、导航栏样式、sitemap 引用 |
app.wxss | 全局样式 | 引入 colorui 样式、重置小程序默认边距 |
pages/ | 页面级逻辑 | 创建、预览、列表、引导页 |
components/ | 可复用组件 | 倒计时圈、图片遮罩、广告占位 |
common/ | 工具函数与常量 | config.js、utils.js、storage 封装 |
static/ | 静态资源 | 图标、背景图、loading 动画 |
colorui/ | UI 组件库 | 按钮、卡片、图标等基础样式 |
这张表是我读完全部源码后整理的,放在这里的作用是给第一次打开工程的人一个导航:改页面样式去pages下对应.wxss,改业务配置去common/config.js,换图标去static/icons。有一个容易踩的坑是colorui的icon类名是固定枚举值,想自定义图标需要去 colorui 的样式文件里找对应uni编码,不能直接拿一个本地 PNG 就替换,否则会出现“图标位置空白”的问题。
3. 闪照核心链路实现:拍照、时间选择到定时销毁
3.1 照片上传与本地临时路径的取舍
闪照本质上是一个“用完即删”的工具,照片不应当被持久化到服务器。这个源码采用的是本地临时路径方案:用wx.chooseMedia选择或拍摄照片后,拿到tempFilePath,直接把它写入preview页的<image>组件。这个路径是微信提供的临时文件路径,小程序退出后就失效,正好符合闪照“不留痕”的产品预期。
// pages/create/create.js async function pickImage() { const res = await wx.chooseMedia({ count: 1, mediaType: ['image'], sourceType: ['album', 'camera'], sizeType: ['compressed'] }); const filePath = res.tempFiles[0].tempFilePath; this.setData({ selectedImage: filePath, imageWidth: res.tempFiles[0].width, imageHeight: res.tempFiles[0].height }); }参数sizeType: ['compressed']很关键,微信会对压缩后的图片做降采样,一般不会超过 2048px,对闪照这种在手机屏幕上最多看 10 秒的图片,压缩反而能减少缩略图渲染卡顿。如果你选择original原图,遇到 12MP 以上的照片,在低端安卓机上渲染<image>时会有肉眼可见的延迟,闪照体验会被拖垮。sourceType同时允许相册和拍照,这个源码默认不锁定拍摄,是因为在微信内调用相机需要用户先授权scope.camera,而相册选择可以绕过这个授权流程,缩短从点击到选完图的时间。
3.2 自定义闪照时间的交互实现
时间选择用的是wx.showActionSheet,这种方式比自定义弹窗省去了大量样式代码,而且在 iOS 和 Android 上的原生视觉效果更稳定。源码里把时间档位设为 3/5/10/30 秒四档,背后的逻辑是:低于 3 秒用户来不及看清,高于 30 秒就失去了“闪”的意义。这个档位配置在common/config.js的SECONDS_OPTIONS数组里,想调整直接改数组就行。
function chooseDuration() { const opts = getApp().globalData.SECONDS_OPTIONS.map(item => `${item}秒`); wx.showActionSheet({ itemList: opts, success: (res) => { const seconds = getApp().globalData.SECONDS_OPTIONS[res.tapIndex]; this.setData({ duration: seconds }); } }); }这里有个设计细节:选完时间后,预览页显示的倒计时其实比用户选的值多 1 秒。原因在于用户点击“发送”按钮到接收方打开图片之间存在网络传输延迟,如果严格按设定秒数销毁,对方打开时可能只剩 2 秒。源码在写入expireAt时额外加了 1000ms 缓冲,这个小改动在实际体验中非常关键。
3.3 定时器与页面生命周期:销毁不是一件简单的事
“闪照销毁”听起来只是一个setTimeout后隐藏图片,但真实实现必须考虑用户退到后台、切到微信对话框、甚至直接锁屏的场景。这个源码在preview页里用的是onShow里启动定时器、onHide里暂停并记录剩余时间、onUnload里清理定时器并清除图片缓存的三段式管理:
// pages/preview/preview.js onLoad(options) { this.startCountdown(); }, onHide() { clearInterval(this.timer); this.setData({ remaining: this.currentRemaining }); }, onUnload() { if (this.timer) clearInterval(this.timer); wx.removeStorageSync(FLASH_CONFIG.STORAGE_KEY); }, startCountdown() { const total = this.data.flashItem.seconds; this.currentRemaining = total; this.timer = setInterval(() => { this.currentRemaining -= 1; this.setData({ remaining: this.currentRemaining }); if (this.currentRemaining <= 0) { clearInterval(this.timer); this.destroyFlashImage(); } }, 1000); }onHide里暂停而非清除定时器,这是一个容易忽略但对体验影响极大的选择。微信小程序的页面在用户点击右上角胶囊按钮返回时,并不会立刻触发onUnload,而是先走onHide。如果onHide里直接清除了定时器,用户从聊天窗口切回来会发现闪照还在,等于绕过销毁机制。正确做法是像上面这样:暂停计时、记录剩余时间,onShow里根据剩余时间恢复倒计时;只有onUnload才真正销毁数据和图片。
destroyFlashImage内部做的事有两件:把预览页<image>的src置空,以及调用wx.removeSavedFile清理本地缓存文件。很多开发者在实现闪照时只做了第一步——图片路径置空,但临时文件还残留在微信的本地缓存目录里。对普通用户来说两者没区别,但在部分安卓机型上,图片路径置空后屏幕会有 0.2 秒的白屏闪烁,配合wx.removeSavedFile之后再执行透明度渐变动画,可以有效规避这个视觉瑕疵。
3.4 合法域名配置:闪照也需要服务器通信吗
很多人以为纯本地图片处理不需要配置域名,但这个源码里实际上包含了一个广告位配置接口,用来动态拉取流量主开关和广告位 ID。这就涉及微信小程序的服务器域名白名单机制。在config.txt和合法域名.txt里,作者记录了请求接口的域名,你需要在微信公众平台后台的“开发管理 → 开发设置 → 服务器域名”里,把request合法域名和downloadFile合法域名都填进去。
| 配置项 | 用途 | 不配置的后果 |
|---|---|---|
request合法域名 | 拉取广告位开关、远程配置 | 接口请求直接失败,白屏 |
downloadFile合法域名 | 加载远程图片、广告素材 | 图片加载不出,Banner 空白 |
uploadFile合法域名 | 如果有图片上传需求才需要 | 本例不需要,可留空 |
需要注意request和downloadFile是两个独立的域名配置项,只配了 request 而没配 downloadFile,广告素材和远程图片就加载不出来。调试期间可以在微信开发者工具的“详情 → 本地设置”里勾选“不校验合法域名”,但提审前必须关闭这个选项,否则真机预览会直接报url not in domain list,这个报错是刷量最多的拦截点。
4. 流量主接入与提审:从代码到上线的完整流程
4.1 流量主能力的前置条件与代码埋点
微信小程序的流量主不是打开就有,需要满足两个硬性条件:小程序累计独立访客(UV)不低于 1000,且无违规记录。这个源码里已经内置了 Banner 和激励视频(Rewarded Video)两种广告位的代码骨架,但没达到条件前,调用wx.createRewardedVideoAd会返回错误码1004,这是正常的——你需要先上线跑量,等 UV 达标后在“小程序后台 → 流量主 → 广告位管理”里创建广告位,拿到adUnitId,回填到common/config.js。
// common/ad.js function createRewardedAd() { const adUnitId = getApp().globalData.REWARD_AD_UNIT_ID; if (!adUnitId) return null; const ad = wx.createRewardedVideoAd({ adUnitId }); ad.onError((err) => { if (err.errCode === 1004) { console.warn('广告组件未初始化完成,降级为无广告模式'); } }); return ad; }代码里对广告创建失败做了降级处理:拿不到adUnitId或广告组件报错时,直接返回null,闪照功能不受影响。这个设计很关键,因为闪照是工具属性,广告是增值属性,不能用广告的失败阻断用户的闪照体验。1004只是未初始化的提示,过几分钟重新拉取即可,不应当把它当成严重 bug 上报。
4.2 Banner 广告位的摆放策略:销毁后再出现
闪照预览结束后,用户看到的是一个销毁动画加一行文案“闪照已销毁”。Banner 广告位如果放在预览页,会破坏倒计时的沉浸感;如果放在首页,曝光率太低。这个源码的做法是把 Banner 放在“销毁完成”这个状态页上,用wx.createBannerAd按需创建,销毁动画结束后才插入页面底部。
showBannerAd() { const ad = wx.createBannerAd({ adUnitId: getApp().globalData.BANNER_AD_UNIT_ID, style: { left: 0, width: 320 } }); ad.onResize((res) => { this.setData({ bannerHeight: res.height, bannerWidth: res.width }); }); }wx.createBannerAd的style只接受left、top、width三个值,top不写的话默认在屏幕顶部。实际部署时我通常会在销毁页用一个<view>占住底部位置,然后onResize回调里拿到广告真实渲染尺寸后动态撑开容器,避免广告被页面自身的overflow: hidden裁掉。Banner 广告的width上限是 320px,超过这个值会自动等比缩放,但底部留白会变大,视觉上不如直接固定 320px 干净。
4.3 提审时容易被拒的细节:隐私协议与内容安全
闪照类小程序天然带“阅后即焚”属性,审核团队会重点检查两个点:用户协议里是否声明了图片处理规则,以及是否有内容安全机制。这个源码的readme.html里给了一份用户协议模板,核心条款是“用户上传的图片仅保存在本地设备,开发者服务器不存储任何用户照片”。这段话必须出现在小程序的“关于”页面或用户协议弹窗里,否则审核有机会以“功能涉及用户隐私且未声明”为由拒审。
内容安全方面,微信审核不会强制要求本地做图片鉴黄,但因为闪照图片是用户生成内容(UGC),建议在提审前在用户协议里加上“禁止上传违法违规内容”的条款,并在小程序里预留一个举报入口。这种做法不一定能保证 100% 过审,但能显著降低被要求补充声明的概率。审核期间建议把流量主广告位暂时关闭,因为广告内容本身也会被纳入审核范围,图片类广告的素材合规性不可控,可能拉长审核周期。
5. 阅后即焚的边界处理:定时器泄漏与截图攻防
5.1 定时器泄漏:扫码进入预览页的隐藏坑
小程序的setInterval不受页面销毁自动清理,这是所有使用定时器的功能页都要面对的问题。闪照功能里最隐蔽的场景是:用户在预览页倒计时期间,通过“识别二维码”跳转到另一个小程序或网页,此时preview页进入onHide,定时器被暂停;如果用户从另一个小程序返回时微信把这个页面从栈里弹出(低端机上内存回收),onUnload里的clearInterval根本不会执行,定时器指针残留在内存里。
规避方式是在util.js里维护一个全局的定时器注册表,页面创建时向app.globalData.timerRegistry注册,页面销毁时统一清理:
// app.js globalData: { timerRegistry: new Set() }, clearAllTimers() { this.globalData.timerRegistry.forEach(timer => { clearInterval(timer); clearTimeout(timer); }); this.globalData.timerRegistry.clear(); }在app.js的onHide生命周期里调用clearAllTimers,相当于给所有闪照定时器加了一道保险。这样做有一个副作用:用户按 Home 键把小程序退到后台,再进来时闪照直接销毁了——但这对闪照功能来说反而是合理行为,阅后即焚的语义就是“离开视线即销毁”,不需要保留现场。
5.2 截图攻防:iOS 截屏监听与安卓的限制
闪照的一类重要威胁是长按截图。微信小程序里wx.onUserCaptureScreen可以监听到用户的截屏行为,这个接口在 iOS 和 Android 上都可用。源码在预览页onLoad时注册了这个监听器,截屏触发时弹出一个模态框,提示“截图将损害闪照的私密性”。注意这只是提示,技术上无法阻止截图——这是微信系统层面的沙箱限制,任何小程序都一样。
更实用的防截屏手段是减少截图的敏感信息。这个源码在闪照图片上叠加了一层半透明噪点遮罩,遮罩透明度在低端安卓机上会随 GPU 渲染能力波动,导致部分机型上截图后的图片比实际显示更清晰。如果你追求更强的防截图效果,可以考虑把图片渲染到<canvas>上再做一次二次绘制,截图会带走被 canvas 处理后的纹理,但代价是渲染性能下降和代码复杂度上升,对 MVP 阶段的小工具不值得。
5.3 从闪照到更多玩法:定时图片分享与群聊场景
这个源码的可扩展方向比大多数工具类小程序更有趣。第一个方向是“限时可见的朋友圈卡片”:把闪照封装成图片,附带一个访问链接,对方点击后在半小时内查看,过期后提示“内容已被撤回”。第二个方向是群聊场景:闪照在群聊里被@指定成员打开后立即销毁,其他群成员只能看到“对方查看了闪照”的记录。这两个方向都只需要在这个源码的preview页基础上加一层业务状态判断,不需要伤筋动骨改架构。
验证方法也很简单:把时间档位从 3/5/10/30 调整为 60/120/300 秒,在开发者工具里用不同网速模拟(Low-end mobile和Mid-tier Android),观察倒计时是否在锁屏和应用切换后依然准确。实测中setInterval在低端机上会有 0.3~1.5 秒的累计漂移,10 秒的闪照实际销毁时间可能在 11 秒左右,这在可接受范围内。你要关注的是漂移是否随着时长线性放大,如果 300 秒时误差超过 15 秒,说明你的页面在后台被微信冻结过,此时用Date.now()差值重算剩余时间,而不是直接恢复原定时器——这也是为什么第 2 章里坚持把expireAt写入本地存储的原因。
本文还有配套的精品资源,点击获取