WordPress适合做多大级别的网站,wordpress分类目录伪静态,企业招聘信息发布平台,正规的网店培训机构有哪些一、引言 在当今社交媒体充斥着各种趣味表情包的时代#xff0c;表情包生成工具成为许多用户创作和分享创意的热门选择。为了迎合这一趋势#xff0c;我设计并开发了一款功能丰富、用户友好的微信小程序#xff0c;旨在让用户能够轻松而有趣地创作个性化的表情包。这篇博客将…一、引言 在当今社交媒体充斥着各种趣味表情包的时代表情包生成工具成为许多用户创作和分享创意的热门选择。为了迎合这一趋势我设计并开发了一款功能丰富、用户友好的微信小程序旨在让用户能够轻松而有趣地创作个性化的表情包。这篇博客将深入介绍我的微信小程序详解其各项功能以及为了提升用户体验而进行的设计和优化。无论是内置的上百种头像素材还是支持自定义上传与编辑的功能我将逐一展示这个小程序的强大之处帮助读者更好地理解并使用这款创意工具。
二、背景 在数字创意时代表情包已经成为人们交流中不可或缺的一部分。然而尽管微信推出了名为“微信创意表情”的小程序我却发现它在素材数量和用户自定义方面存在一些限制。素材有限、用户无法自定义上传图片这些限制让我看到了改进的空间因此萌发了开发自己的表情包生成微信小程序的念头。 我的小程序的背景正是在这样的需求和不足之下诞生的。通过深入研究用户反馈和市场调研我决定打造一款更加强大、灵活且富有创意的表情包生成工具。我的初衷是为用户提供一个自由、个性化的创作空间让他们能够尽情表达自己的创意而不受素材和功能的限制。通过模仿并超越现有的小程序我致力于打破创作的边界让用户在表情包的世界中享受更多的乐趣和创意。这款小程序不仅仅是一款工具更是我对创意自由和个性表达的热忱回应。
三、已实现功能
1、内置上百种头像合成素材
提供丰富多样的素材包括脸、嘴、眼、身体、手、网络脸、挂架等供用户选择和合成。
2、可自定义上传图片
允许用户自由上传个人照片或网络素材拓展创作空间让表情包更具个性。
3、图片支持移动、缩放、旋转、批量选择
提供直观的操作界面用户可以自由调整图片的位置、大小、旋转角度并支持批量选择多个元素进行统一操作。
4、图层可以通过列表拖拽实现上下排序
实现直观的图层管理用户可通过简单的拖拽操作调整图层的顺序灵活掌握表情包的层次结构。
5、不同颜色、触点大小的涂鸦
提供丰富的涂鸦工具用户可以自由选择不同颜色和触点大小为表情包添加更多创意元素。
6、文字输入
实现文字输入功能用户可选择不同颜色的文字自由添加文字表达情感或创意。
7、图层自由调整透明度
用户可以对每个图层进行透明度调整实现更丰富的叠加效果。
8、图层左右镜像
提供左右镜像功能增加表情包的变化和趣味性。
9、图层复制
允许用户复制已有的图层方便创作时的重复元素使用。
10、将合成的图片导出
提供简便的导出功能让用户能够保存和分享自己创作的表情包。
四、素材库设计
我定义了一个组件”EmojiCardPanel“从网上搜罗了许多素材然后获取到所有素材的宽、高。定义素材相对于图层左上角的坐标值。实现效果。
上层是tab下层是 swiperscroll-view 代码如下
script setup langts
import { onMounted, ref } from vue
const emits defineEmits{//选择emoji(e: selectEmoji, x: number, y: number, w: number, h: number, url: string): void
}()/** Tab 选项 */
type TabOption {id: numbertitle: stringdetails: TabDetail[]
}/** Tab 明细 */
type TabDetail {url: stringx: numbery: numberw: numberh: number
}// 所有tab
const tabs refTabOption[]([])
// 当前选择Tab Index
const activeTabIndex refnumber(-1)
// 选择图片Id
const currentTouchImageId refnumber(-1)onMounted(() {tabs.value [在这里填充tab数据]activeTabIndex.value 0
})// 选择tab
const onSelectTab (index: number) {activeTabIndex.value index
}// image touch start
const onImageTouchStart (img: TabDetail, index: number) {currentTouchImageId.value index
}// image tap
const onTap (img: TabDetail, index: number) {currentTouchImageId.value indexuni.downloadFile({url: img.url,success: (res) {if (res.statusCode 200) {emits(selectEmoji, img.x, img.y, img.w, img.h, res.tempFilePath)currentTouchImageId.value -1} else {uni.showToast({title: 下载资源失败状态码 res.statusCode,})currentTouchImageId.value -1}},fail: (error) {uni.showToast({title: 资源下载异常,})currentTouchImageId.value -1},})
}// image touch end
const onImageTouchEnd () {currentTouchImageId.value -1
}
/script
templateview classemoji-card-panelview classtabstextclasstext:class{ select: index activeTabIndex }v-for(tab, index) in tabs:keytab_ indextaponSelectTab(index){{ tab.title }}/text/viewswiperclassswiper:currentactiveTabIndexchangeactiveTabIndex $event.detail.currentswiper-item v-for(tab, index) in tabs :keydetail_ indexscroll-view classtab-detail scroll-yview classimagesimageclassimage:lazy-loadtruemodewidthFix:class{ touch: ix currentTouchImageId }taponTap(img, ix)touchstartonImageTouchStart(img, ix)touchendonImageTouchEndv-for(img, ix) in tab.details:keyimg_ ix:srcimg.url/image/view/scroll-view/swiper-item/swiper/view
/template
style langscss scoped
.emoji-card-panel {height: 100%;width: 100%;display: flex;flex-direction: column;padding-top: 5px;font-size: 13px;.tabs {display: flex;justify-content: space-between;align-items: center;height: 40rpx;margin: 10px;.text {padding: 3px 10px;border-radius: 15px;.select {background-color: #07c160;color: white;/* 过渡效果 */transition: all 0.4s;}}}.tab-detail {flex: 1;.images {display: flex;flex-wrap: wrap;justify-content: space-between;padding: 0 10rpx 10rpx;.image {width: 40px;padding: 10px;border-radius: 12px;margin: 5px;.touch {background-color: rgba(0, 0, 0, 0.1);}}.image:last-child {margin-right: auto;}}}
}
/style五、自定义上传与编辑
这个就不用说了选择图片上传然后图片绘制到最上层图层并默认选中 六、图层管理
针对图层管理是最复杂的同样也增加了一个组件”CanvasDragPanel“没有使用 2d版本的canvas 虽然2d渲染速度更快但是发现图层在移动、缩放时有闪烁不管是用双缓冲还是requestAnimationFrame 都未能解决这个闪烁问题。因为要涂鸦所以用了两个canvas在涂鸦时将主canvas隐藏涂鸦结束后再调换过来。
这里的难点是图层选择、移动、缩放、删除、并且我还设计了批量选择这些都将是此小程序实现的难点。
templateviewclasscanvas-drag-panel:style{width: props.width px,height: props.height px,}!--主canvas--canvasidmainCanvasIdcanvas-idmainCanvasId:class{disabled: mainCanvasDisabled,}classcanvas main-canvastouchstarttouchStarttouchmovetouchMovetouchendtouchEnderrortouchErrortouchcanceltouchCancel:disable-scrolltrue/canvas!--涂鸦canvas、将文本转换图片canvas--canvasv-showgraffitiCanvasShowcanvas-idgraffitiCanvasIdtouchstartgraffitiTouchStarttouchmovegraffitiTouchMovetouchendgraffitiTouchEnd:disable-scrolltrueclasscanvas graffiti-canvas/canvas/view
/template
七、涂鸦和文字输入
涂鸦和文字输入也是封装了两个组件分别是”GraffitiPanel“和”AddTextPanel“至于选择涂鸦还是文字因为是一个按钮组和后面的调节是一样的封装了一个组件”ButtonGroupPanel“ 涂鸦面板效果 代码如下
script setup langts
import { ref } from vue
const emits defineEmits{// 取消(e: cancelGraffiti): void// 完成(e: completeGraffiti): void// 选择stroke(e: selectGraffitiStroke, index: number): void// 选择color(e: selectGraffitiColor, index: number): void
}()// 定义组件属性
const props defineProps{top: numberheight: numberstrokeDataList: number[]colorDataList: string[]
}()// 取消
const cancelGraffiti () {emits(cancelGraffiti)
}
// 完成
const endGraffiti () {emits(completeGraffiti)
}// 选择 stroke
const selectGraffitiStroke (index: number) {currentGraffitiStrokeIndex.value indexemits(selectGraffitiStroke, index)
}// 选择yanse
const selectGraffitiColor (index: number) {emits(selectGraffitiColor, index)currentGraffitiColorIndex.value index
}// 当前选择stroke
const currentGraffitiStrokeIndex refnumber(1)
// 当前选择颜色
const currentGraffitiColorIndex refnumber(1)
/script
templateviewclassgraffiti-panel:style{top: props.top px,height: props.height px,}view classtoptext classleft tapcancelGraffiti取消/texttext classcenter涂鸦/texttext classright tapendGraffiti完成/text/viewview classmainview classtitletext笔触/text/viewview classcontentviewclasscircle-view stroke-view:class{selected: index currentGraffitiStrokeIndex,}tapselectGraffitiStroke(index)v-for(item, index) in props.strokeDataList:keystroke_ indexviewclasscircle:style{width: item px,height: item px,background-color: colorDataList[currentGraffitiColorIndex],}/view/view/viewview classtitletext颜色/text/viewview classcontentviewclasscircle-view color-view:class{selected: index currentGraffitiColorIndex,}tapselectGraffitiColor(index)v-for(item, index) in props.colorDataList:keycolor_ indexviewclasscircle:style{background-color: item,}/view/view/view/view/view
/template
style langscss scoped
.graffiti-panel {margin: 0px 5px;left: 0;right: 0;box-shadow: 0px 0px 10px rgba(135, 131, 131, 0.4);position: absolute;border-top-right-radius: 10px;border-top-left-radius: 10px;background-color: white;z-index: 998;.top {display: flex;height: 40px;justify-content: center;align-items: center;margin: 5px 10px;text-align: center;.left,.right {flex: 0 0 50px;padding: 10px;}.center {flex: 1;}}.main {margin: 20px;.title {height: 40px;line-height: 40px;color: darkgrey;}.content {display: flex;justify-content: center;align-items: center;.circle-view {border-radius: 50%;margin: 5px;.stroke-view {width: 40px;height: 40px;display: flex;margin: 10px;align-items: center;justify-content: center;background-color: rgba(0, 0, 0, 0.1);.selected {width: 48px;height: 48px;margin: 2px;}}.color-view {width: 30px;height: 30px;margin: 4px;border: 4px solid rgba(0, 0, 0, 0.1);.selected {width: 34px;height: 34px;margin: 2px;border: 8px solid rgba(0, 0, 0, 0.1);}.circle {height: 100%;width: 100%;}}.circle {border-radius: 50%;}}}}
}
/style文字输入效果如下可以选择子颜色 八、透明度、翻转、复制
透明度、翻转、复制同样用到了按钮组组件”ButtonGroupPanel“只是如果未选择图层三个按钮是置灰状态 因为我用的是非 2d canvas 所以 不支持 ”context.scale(-1, 1)“ 但是水平翻转这行代码是关键就用了”createOffscreenCanvas“ 翻转后将canvas存入临时文件放入图层列表。
九、图层上下顺序调整、删除
此功能增加了一个单独组件”ListDragPanel“可以删除图层、按住行尾图标可实现图层上下顺序调整 代码如下
script setup langts
import { reactive, ref } from vue
const emits defineEmits{//删除拖拽项(e: delDragItem, id: number): void//选择拖拽项(e: selectDragItem, id: number): void//拖拽项顺序调整(e: changeDragItemOrder, oneId: number, towId: number): void
}()/** 拖拽列表明细类型声明 */
type DragListItem {id: numberurl: string
}/** 状态类型声明 */
type StateType {// 拖拽列表dragList: DragListItem[]// 初始化明细 左上角坐标initListTop: number[]// 最新明细 左上角坐标newListTop: number[]// 记录当前手指的垂直方向的坐标touchY: number// 记录当前操作的item数据currentItemTop: number// 当前操作的item的下标currentItemIndex: number// 当前选择item idcurrentSelectedId: number// 控制ScrollView 是否可滚动dragListContainerCanScroll: boolean
}// 状态控制
const state reactiveStateType({dragList: [],initListTop: [],// 坐标数据newListTop: [],// 记录当前手指的垂直方向的坐标touchY: 0,currentItemTop: 0,// 当前操作的item的下标currentItemIndex: -1,// 当前选择item idcurrentSelectedId: -1,// 控制ScrollView 是否可滚动dragListContainerCanScroll: true,
})
// 拖拽项 每一项高度
const ITEM_HEIGHT 55// 获取所有项 top
const getDragItemTop (): number[] {return state.dragList.map((item, i) {return i * ITEM_HEIGHT})
}// 触摸开始
const onTouchStart (event: any, index: number) {state.currentSelectedId state.dragList[index].idstate.dragListContainerCanScroll falseconst [{ pageY }] event.touchesstate.currentItemIndex indexstate.touchY pageYstate.currentItemTop state.newListTop[index]uni.vibrateShort({type: heavy,})
}// 触摸移动
const onTouchMove (event: any) {const [{ pageY }] event.touchesconst current state.newListTop[state.currentItemIndex]const prep state.newListTop[state.currentItemIndex - 1]const next state.newListTop[state.currentItemIndex 1]// 获取移动差值state.newListTop[state.currentItemIndex] current (pageY - state.touchY)// 记录手指坐标state.touchY pageY// 向下移动超过下一个的1/2就进行换位if (next current next - ITEM_HEIGHT / 2) {changePosition(state.currentItemIndex 1)} else if (prep current prep ITEM_HEIGHT / 2) {// 向上移动超过上一个的1/2就进行换位changePosition(state.currentItemIndex - 1)}
}// 触摸结束
const onTouchEnd () {touchFinaly()
}// 触摸异常关闭
const onTouchCancel () {touchFinaly()
}// 触摸最终执行
const touchFinaly () {// 将拖拽的item归位state.newListTop[state.currentItemIndex] state.initListTop[state.currentItemIndex]state.currentItemIndex -1state.dragListContainerCanScroll true
}// 交换位置
const changePosition (index: number) {// 记录当前拖拽的item数据const tempItem state.dragList[state.currentItemIndex]const oneId: number tempItem.idconst twoId: number state.dragList[index].id// 设置原来位置的itemstate.dragList[state.currentItemIndex] state.dragList[index]// 将临时存放的数据设置好state.dragList[index] tempItememits(changeDragItemOrder, oneId, twoId)// 调整位置itemstate.newListTop[index] state.newListTop[state.currentItemIndex]state.newListTop[state.currentItemIndex] state.currentItemTop// 改变当前操作的的下标state.currentItemIndex index// 记录新位置的数据state.currentItemTop state.initListTop[state.currentItemIndex]uni.vibrateShort({type: heavy,})
}// 删除拖拽明细
const onDelDragItem (index: number) {const delItem: DragListItem state.dragList[index]state.dragList.splice(index, 1)state.currentSelectedId -1state.initListTop getDragItemTop()state.newListTop getDragItemTop()emits(delDragItem, delItem.id)
}// 选择拖拽项
const onSelectDragItem (index: number) {const selectedItem: DragListItem state.dragList[index]state.currentSelectedId selectedItem.idemits(selectDragItem, selectedItem.id)
}// 添加拖拽项
const addDragItem (id: number, url: string) {state.dragList.splice(0, 0, {id: id,url: url,})const top: number (state.dragList.length - 1) * ITEM_HEIGHTstate.initListTop getDragItemTop()state.newListTop getDragItemTop()state.currentSelectedId id
}// 删除拖拽项
const delDragItem (id: number) {const index: number state.dragList.findIndex((item) item.id id)state.dragList.splice(index, 1)state.initListTop getDragItemTop()state.newListTop getDragItemTop()
}// 选择拖拽项
const selectDragItem (id: number) {state.currentSelectedId id
}// 暴露函数
defineExpose({addDragItem,delDragItem,selectDragItem,
})
/scripttemplateview classlist-drag-panelscroll-viewclassdrag-list-scroll:scroll-ystate.dragListContainerCanScroll:style{ height: 100% }viewclassdrag-list-item:class{ selected: state.currentSelectedId item.id }v-for(item, index) in state.dragList:style{top: state.newListTop[index] px,}:keydrag-list-item_ indexview classdrag-list-item-item drag-list-item-left taponDelDragItem(index)image classremove-icon lazy-load src/static/images/del.png modewidthFix/image/viewview classdrag-list-item-item drag-list-item-center taponSelectDragItem(index)image classlayer-img lazy-load :srcitem.url modeaspectFit /text图层{{ index 1 }}/text/viewviewclassdrag-list-item-item drag-list-item-righttouchstartonTouchStart($event, index)touchmoveonTouchMovetouchendonTouchEndtouchcancelonTouchCancelimage classdrag-icon lazy-load src/static/images/drag.png modewidthFix/image/view/view/scroll-view/view
/templatestyle langscss scoped
.list-drag-panel {height: 100%;.drag-list-scroll {height: 100%;width: 100%;overflow-y: auto;position: relative;.drag-list-item {display: flex;position: absolute;left: 0;right: 0;justify-content: space-between;align-items: center;padding-right: 5px;margin: 0px 5px;.selected {background-color: #f0fff0;border-radius: 10px;}.drag-list-item-left {padding: 5px 10px;height: 45px;display: flex;justify-content: center;align-items: center;.remove-icon {width: 20px;height: 20px;padding-right: 5px;}}.drag-list-item-center {width: 100%;padding: 5px 0px;border-bottom: 1px solid rgba(0, 0, 0, 0.1);.layer-img {width: 40px;height: 40px;margin: 2px 15px 2px 0px;}}.drag-list-item-item:last-child {margin-left: auto;}.drag-list-item-right {padding: 0px 10px;height: 55px;display: flex;border-bottom: 1px solid rgba(0, 0, 0, 0.1);justify-content: center;align-items: center;.drag-icon {width: 20px;height: 20px;}}}}
}
/style这里的问题点是 要动态控制”scroll-view“ 是否可滚动在移动图层顺序时是不允许滚动的。
十、导出与分享
将主canvas导出这里的问题是因为在选中和居中时会有特殊线条希望在导出时重新绘制图层可发现有时候重新绘制图层位置会偏移未找到问题。 十一、未来发展和优化方向
当前这个小程序已上线有兴趣的可以搜索进去看看有疑问的大家可以留言交流。 目前此小程序未实现 2d canvas后续可以研究下2d
后续继续增加undo 效果。
比较鸡肋的是不能直接将制作的表情加入微信聊天中腾讯是真坑自家小程序可以未开放其他人。
好了大家有疑问的可以留言沟通哈。