{"id":13671449,"url":"https://github.com/yeliudev/LemonJournal","last_synced_at":"2025-04-27T18:31:19.400Z","repository":{"id":47074095,"uuid":"138060733","full_name":"yeliudev/LemonJournal","owner":"yeliudev","description":"A WeChat mini program demo based on Wafer2 framework - 微信小程序Demo: 柠檬手帐 - 图片编辑应用，支持图片和文字的移动、旋转、缩放、保存编辑状态并生成预览图","archived":false,"fork":false,"pushed_at":"2021-09-14T18:42:14.000Z","size":4416,"stargazers_count":393,"open_issues_count":0,"forks_count":75,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-04-06T20:11:22.495Z","etag":null,"topics":["canvas","weapp","wechat"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yeliudev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-20T16:48:38.000Z","updated_at":"2025-03-03T10:40:29.000Z","dependencies_parsed_at":"2022-08-30T22:30:26.728Z","dependency_job_id":null,"html_url":"https://github.com/yeliudev/LemonJournal","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeliudev%2FLemonJournal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeliudev%2FLemonJournal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeliudev%2FLemonJournal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeliudev%2FLemonJournal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yeliudev","download_url":"https://codeload.github.com/yeliudev/LemonJournal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251187135,"owners_count":21549593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["canvas","weapp","wechat"],"created_at":"2024-08-02T09:01:09.965Z","updated_at":"2025-04-27T18:31:18.157Z","avatar_url":"https://github.com/yeliudev.png","language":"JavaScript","readme":"# LemonJournal\n\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Language](https://img.shields.io/badge/javascript-ES6-orange.svg)](https://www.javascript.com/)\n\n该项目基于 [Wafer2](https://github.com/tencentyun/wafer2-startup) 框架开发，后台采用腾讯云提供的 Node.js SDK 接入对象存储 API ，前端核心代码实现了图片编辑器的功能，支持图片和文字的移动、旋转、缩放、生成预览图以及编辑状态的保存，动画部分采用 CSS 动画实现\n\n小程序中的模态输入框为单独封装的 [InputBox](https://github.com/yeliudev/weapp-inputbox) 组件\n\n代码已移除 AppId 等敏感信息，可自行添加自己的 AppId 和 AppSecret 以配置后台环境，实现登录测试，详细添加方法见下文「使用方法」，若本地运行可通过修改 `app.json` 文件中 `page` 字段的顺序来查看不同页面\n\n## 效果展示\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot1.png\" hspace=\"30px\" /\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot2.png\" hspace=\"30px\" /\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot3.png\" hspace=\"30px\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot4.png\" hspace=\"30px\" /\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot5.png\" hspace=\"30px\" /\u003e\n  \u003cimg width=\"200\" src=\".github/screenshot6.png\" hspace=\"30px\" /\u003e\n\u003c/p\u003e\n\n## 使用方法\n\n* 获取Demo代码\n    * 执行 `git clone https://github.com/yeliudev/LemonJournal.git`\n* 下载后在微信开发者工具中打开 `LemonJournal` 文件夹即可\n\n#### 如需进行登录测试，还要执行以下步骤\n\n* 准备好自己的 AppId 和 AppSecret（可在微信公众平台注册后获取）\n* 在 `project.config.json` 的 `appid` 字段中填入 AppId\n* 在 `/client/utils/util.js` 中相应位置填入 AppId 和 AppSecret\n* 在微信开发者工具中重新导入整个项目，上传后台代码后编译运行即可\n\n## 核心代码\n\n#### 组件的移动、旋转和缩放\n\n* 主要思路为将 `\u003cimage\u003e` 标签（对应图片）和 `\u003ctext\u003e` 标签（对应文字）封装在同一个自定义组件 `\u003csticker\u003e` 中，通过对外暴露的 `text` 变量是否为空来进行条件渲染，然后绑定 `onTouchStart()` 、`onTouchEnd()` 和 `onTouchMove()` 三个事件来对整个组件的位置、角度、大小、层级以及 “旋转” 和 “移除” 两个按钮的行为进行操作\n\n``` js\nonTouchStart: function (e) {\n    // 若未选中则直接返回\n    if (!this.data.selected) {\n        return\n    }\n\n    switch (e.target.id) {\n        case 'sticker': {\n            this.touch_target = e.target.id\n            this.start_x = e.touches[0].clientX * 2\n            this.start_y = e.touches[0].clientY * 2\n            break\n        }\n        case 'handle': {\n            // 隐藏移除按钮\n            this.setData({\n                hideRemove: true\n            })\n\n            this.touch_target = e.target.id\n            this.start_x = e.touches[0].clientX * 2\n            this.start_y = e.touches[0].clientY * 2\n\n            this.sticker_center_x = this.data.stickerCenterX;\n            this.sticker_center_y = this.data.stickerCenterY;\n            this.remove_center_x = this.data.removeCenterX;\n            this.remove_center_y = this.data.removeCenterY;\n            this.handle_center_x = this.data.handleCenterX;\n            this.handle_center_y = this.data.handleCenterY;\n\n            this.scale = this.data.scale;\n            this.rotate = this.data.rotate;\n            break\n        }\n    }\n},\n\nonTouchEnd: function (e) {\n    this.active()\n    this.touch_target = ''\n\n    // 显示移除按钮\n    this.setData({\n        removeCenterX: 2 * this.data.stickerCenterX - this.data.handleCenterX,\n        removeCenterY: 2 * this.data.stickerCenterY - this.data.handleCenterY,\n        hideRemove: false\n    })\n\n    // 若点击移除按钮则触发移除事件，否则触发刷新数据事件\n    if (e.target.id === 'remove') {\n        this.triggerEvent('removeSticker', this.data.sticker_id)\n    } else {\n        this.triggerEvent('refreshData', this.data)\n    }\n},\n\nonTouchMove: function (e) {\n    // 若无选中目标则返回\n    if (!this.touch_target) {\n        return\n    }\n\n    var current_x = e.touches[0].clientX * 2\n    var current_y = e.touches[0].clientY * 2\n    var diff_x = current_x - this.start_x\n    var diff_y = current_y - this.start_y\n\n    switch (e.target.id) {\n        case 'sticker': {\n            // 拖动组件则所有控件同时移动\n            this.setData({\n                stickerCenterX: this.data.stickerCenterX + diff_x,\n                stickerCenterY: this.data.stickerCenterY + diff_y,\n                removeCenterX: this.data.removeCenterX + diff_x,\n                removeCenterY: this.data.removeCenterY + diff_y,\n                handleCenterX: this.data.handleCenterX + diff_x,\n                handleCenterY: this.data.handleCenterY + diff_y\n            })\n            break\n        }\n        case 'handle': {\n            // 拖动操作按钮则原地旋转缩放\n            this.setData({\n                handleCenterX: this.data.handleCenterX + diff_x,\n                handleCenterY: this.data.handleCenterY + diff_y\n            })\n\n            var diff_x_before = this.handle_center_x - this.sticker_center_x;\n            var diff_y_before = this.handle_center_y - this.sticker_center_y;\n            var diff_x_after = this.data.handleCenterX - this.sticker_center_x;\n            var diff_y_after = this.data.handleCenterY - this.sticker_center_y;\n            var distance_before = Math.sqrt(diff_x_before * diff_x_before + diff_y_before * diff_y_before);\n            var distance_after = Math.sqrt(diff_x_after * diff_x_after + diff_y_after * diff_y_after);\n            var angle_before = Math.atan2(diff_y_before, diff_x_before) / Math.PI * 180;\n            var angle_after = Math.atan2(diff_y_after, diff_x_after) / Math.PI * 180;\n\n            this.setData({\n                scale: distance_after / distance_before * this.scale,\n                rotate: angle_after - angle_before + this.rotate\n            })\n            break\n        }\n    }\n\n    this.start_x = current_x;\n    this.start_y = current_y;\n}\n```\n\n#### 编辑状态的保存\n\n* 一篇手帐包含的组件类型包括 `sticker`（软件自带的贴纸）、`image`（用户上传的图片）和 `text`（自定义文字）三种，全部保存在一个如下格式的 json 对象中，每个独立组件都包含了一个不重复的 id 以及相关的信息，保存时由客户端生成该对象并编码成 json 字符串存储在数据库，恢复编辑状态时通过解析 json 字符串获得对象，再由编辑页面渲染\n\n``` js\n{\n    \"backgroundId\": \"5\",                                        背景图id\n    \"assemblies\": [\n        {\n            \"id\": \"jhjg\",                                       组件id\n            \"component_type\": \"image\",                          组件类型（自定义图片）\n            \"image_url\": \"https://example.com/jhjg.png\",        图片地址\n            \"stickerCenterX\": 269,                              中心横坐标\n            \"stickerCenterY\": 664,                              中心纵坐标\n            \"scale\": 1.7123667831396403,                        缩放比例\n            \"rotate\": -3.0127875041833434,                      旋转角度\n            \"wh_scale\": 1,                                      图片宽高比\n            \"z_index\": 19                                       组件层级\n        },\n        {\n            \"id\": \"gs47\",\n            \"component_type\": \"text\",                           组件类型（文字）\n            \"text\": \"test\",                                     文字内容\n            \"stickerCenterX\": 479,\n            \"stickerCenterY\": 546,\n            \"scale\": 1.808535318980528,\n            \"rotate\": 29.11614626607893,\n            \"z_index\": 10\n        },\n        {\n            \"id\": \"chjn\",\n            \"component_type\": \"sticker\",                        组件类型（贴纸）\n            \"sticker_type\": \"food\",                             贴纸类型\n            \"sticker_id\": \"1\",                                  贴纸id\n            \"image_url\": \"https://example.com/weapp/stickers/food/1.png\",\n            \"stickerCenterX\": 277,\n            \"stickerCenterY\": 260,\n            \"scale\": 1.3984276885130673,\n            \"rotate\": -16.620756913892055,\n            \"z_index\": 5\n        }\n    ]\n}\n```\n\n## License\n\n[MIT License](LICENSE)\n\nCopyright (c) 2021 Ye Liu\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeliudev%2FLemonJournal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyeliudev%2FLemonJournal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeliudev%2FLemonJournal/lists"}