记录--Vue3 + Fabricjs 定制国庆专属头像

这篇具有很好参考价值的文章主要介绍了记录--Vue3 + Fabricjs 定制国庆专属头像。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--Vue3 + Fabricjs 定制国庆专属头像

记录--Vue3 + Fabricjs 定制国庆专属头像

生在国旗下,长在春风里!国庆将至,采黎为大家带来 定制头像2.0(国庆头像),让我们用代码的形式为祖国庆生!欢迎大家点赞收藏加关注哦

前言

想看效果或者想定制春节头像的小伙伴请直奔 效果区域;
想一睹定制头像2.0小工具的原理及实现思路请耐心阅读,本文代码片段较多~

在线定制

🚀🚀🚀 定制头像入口, 体验地址 🚀🚀🚀

🚀🚀🚀 github项目地址(欢迎⭐) 🚀🚀🚀

喜欢这个小工具的话,动动小手点个star⭐哦,谢谢!

关于迭代

定制兔年春节头像 上线后,很多小伙伴体验后第一时间就给了建议、反馈;在大家的帮助下,工具也在不断的完善;比如导出图片不够清晰、不能设置透明度等等,迭代到1.4.0后,已经可以保证正常的使用了,这里采黎给大家说声谢谢!

由于当时聚焦在兔年春节头像上,工具风格单一,功能还不够完善,内部逻辑有点大材小用等等,于是便有了大版本的定制头像2.0迭代。

更新内容

仓库名称

  • custom-rabbitImage 改为 custom-avatar

页面

  • 重构页面整体风格,调整为通用型风格
  • 兼容pc、移动端
  • 移动端头像墙采用瀑布流

画布相关

  • 用户上传的原图做短边适配,保证不变形
  • 优化元素控件效果,增加删除控件
  • 优化绘制逻辑,减少无用运算。

新增功能

  • 增加多主题选项(中秋节、国庆节、春节等,其他传统节日敬请期待)
  • 增加贴纸效果,可多选、可删除
  • 增加快速切换头像框功能
  • 增加通知功能(xx用户在3分钟前定制了国庆头像)
  • 增加分享海报功能
  • 增加头像墙功能,用户可预览他人定制的头像

修复已知问题

  • 修复qq浏览器无法选择文件
  • 修复微信浏览器无法保存图片

项目架构

vue3 | vite | ts | less | Elemenu UI | eslint | stylelint | husky | lint-staged | commitlint

所需素材

头像框、贴纸正在设计中,会一点一点补起来。

中秋主题

记录--Vue3 + Fabricjs 定制国庆专属头像

国庆主题

记录--Vue3 + Fabricjs 定制国庆专属头像

春节主题

记录--Vue3 + Fabricjs 定制国庆专属头像

思路

基本思路不变,定制兔年春节头像中已经讲过,这里就不再赘述了。

画布交互逻辑优化

这是第一版的逻辑梳理

记录--Vue3 + Fabricjs 定制国庆专属头像

考虑到定制头像工具图层不会过多,功能不会太复杂,于是 在新版中做了如下优化

  • 删除绘制多个图层逻辑(监听图层列表变化,进而绘制图层)
  • 绘制头像框改为主动调用,减少无用调用频次;
  • 绘制贴纸为主动调用,可绘制多个
  • 删除画布操作同步逻辑(不需要回显数据到页面,也不用二次绘制,故删除)

做完上述优化后,代码量明显下来了;只怪当时没有过多的思考,就将其他项目的实现方式生搬硬套了。

代码实现

画布

  1. 初始化画布及控件
const init = () => {
    /* 初始化控件 */
    initFabricControl()

    /* 初始化画布 */
    Canvas = initCanvas(CanvasId.value, canvasSize, false)

    // 元素缩放事件
    Canvas.on('object:scaling', canvasMouseScaling)
}


/* 初始化控件 */
const initFabricControl = () => {
    fabric.Object.prototype.set(control)
    // 设置缩放摇杆偏移
    fabric.Object.prototype.controls.mtr.offsetY = control.mtrOffsetY
    // 隐藏不需要的控件
    hiddenControl.map((name: string) => (fabric.Object.prototype.controls[name].visible = false))

    /* 添加删除控件 */
    const delImgElement = document.createElement('img')
    delImgElement.src = new URL('./icons/delete.png', import.meta.url).href

    const size = 52

    const deleteControlHandel = (e, transform:any) => {
        const target = transform.target
        const canvas = target.canvas
        canvas.remove(target).renderAll()
    }

    const renderDeleteIcon = (ctx:any, left:any, top:any, styleOverride:any, fabricObject:any) => {
        ctx.save()
        ctx.translate(left, top)
        ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
        ctx.drawImage(delImgElement, -size / 2, -size / 2, size, size)
        ctx.restore()
    }

    fabric.Object.prototype.controls.deleteControl = new fabric.Control({
        x: 0.5,
        y: -0.5,
        cornerSize: size,
        offsetY: -48,
        offsetX: 48,
        cursorStyle: 'pointer',
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        mouseUpHandler: deleteControlHandel,
        render: renderDeleteIcon
    })
}
  1. 监听原图(用户上传的头像)改变,并进行短边适配
/* 更改原图 */
watch(() => props.bg, async (val) => (await drawBackground(Canvas, val)))

/**
 * @function drawBackground 绘制背景
 * @param { Object } Canvas 画布实例
 * @param { String } bgUrl 用户上传得原图片链接
 */
export const drawBackground = async (Canvas, bgUrl: string) => {
    return new Promise((resolve: any) => {
        if (!bgUrl) return resolve()

        fabric.Image.fromURL(bgUrl, (img: any) => {

            img.set({
                left: Canvas.width / 2,
                top: Canvas.height / 2,
                originX: 'center',
                originY: 'center'
            })

            /* 短边适配 */
            img.width > img.height ? img.scaleToHeight(Canvas.height, true) : img.scaleToWidth(Canvas.width, true)
            Canvas.setBackgroundImage(img, Canvas.renderAll.bind(Canvas))

            resolve()
        }, { crossOrigin: 'Anonymous' })
    })
}
  1. 绘制头像框,并隐藏删除按钮控件
const frameName = 'frame'

/**
 * @function addFrame 添加头像框图层
 * @param { String } url 头像框链接
 */
const addFrame = async (url = '') => {
    if (!url) return

    const frameLayer: any = await drawImg(`${ url }!frame`)
    frameLayer.set({
        left: Canvas.width / 2,
        top: Canvas.height / 2
    })

    /* 隐藏删除按钮 */
    frameLayer.setControlVisible('deleteControl', false)

    frameLayer.scaleToWidth(Canvas.width, true)

    frameLayer.name = frameName
    addOrReplaceLayer(Canvas, frameLayer)
}
  1. 设置头像框透明度
/**
 * @function setFrameOpacity 设置头像框透明度
 * @param { Number } opacity 透明度
 */
const setFrameOpacity = (opacity = 1) => {
    const frameLayer: any = findCanvasItem(Canvas, frameName)[1] || ''

    if (!frameLayer) return

    frameLayer.set({ opacity })
    Canvas.renderAll()
}
  1. 绘制贴纸
/**
 * @function addMark 添加贴纸
 * @param { String } url 贴纸链接
 */
const addMark = async (url) => {
    if (!url) return

    const markLayer: any = await drawImg(url)
    markLayer.set({
        left: Canvas.width / 2,
        top: Canvas.height / 2
    })

    markLayer.width > markLayer.height ? markLayer.scaleToHeight(200, true) : markLayer.scaleToWidth(200, true)

    markLayer.name = `mark-${ createUuid() }`
    addOrReplaceLayer(Canvas, markLayer)
}
  1. 保存图片,导出base64
/**
 * @function save 保存效果图
 * @return { String } result base64 保存/预览时返回
 */
const save = async (): Promise<string> => {
    return Canvas.toDataURL({
        format: 'png',
        left: 0,
        top: 0,
        width: Canvas.width,
        height: Canvas.height
    })
}

现在代码明朗了很多,犹如柳暗花明。

页面交互

  1. 用户上传图片,生成本地短链,然后绘制原头像,并默认绘制第一个头像框。
const uploadFile = async (e: any) => {
    if (!e.target.files || !e.target.files.length) return ElMessage.warning('上传失败!')

    const file = e.target.files[0]
    if (!file.type.includes('image')) return ElMessage.warning('请上传正确的图片格式!')

    const url = getCreatedUrl(file) ?? ''
    /* 用户初次上传头像默认选中第一个头像框 */
    if (!originAvatarUrl.value) {
        originAvatarUrl.value = url
        selectFrame(0)
    } else {
        originAvatarUrl.value = url
    }

    (document.getElementById('uploadImg') as HTMLInputElement).value = ''
}
  1. 用户点击头像框或点击快速切换按钮,绘制头像框
/* 快速切换头像框 */
const changeFrame = (isNext) => {
    if (!originAvatarUrl.value) return ElMessage.warning('请先上传头像!')

    const frameList =  picList[styleIndex.value].frameList
    if (isNext) {
        (selectFrameIndex.value === frameList.length - 1) ? selectFrameIndex.value = 0 : (selectFrameIndex.value as number)++
    } else {
        (selectFrameIndex.value === 0) ? selectFrameIndex.value = frameList.length - 1 : (selectFrameIndex.value as number)--
    }
    selectFrame(selectFrameIndex.value as number)
}

/* 绘制头像框-调用画布绘制函数 */
const selectFrame = (index: number) => {
    if (!originAvatarUrl.value) return ElMessage.warning('请先上传头像!')

    opacity.value = 1
    selectFrameIndex.value = index
    frameUrl.value = picList[styleIndex.value].frameList[index]
    DrawRef.value.addFrame(frameUrl.value)
}
  1. 设置头像框透明度
const opacity = ref<number>(1)
const opacityChange = (num: number) => DrawRef.value.setFrameOpacity(num)
  1. 点击贴纸,绘制贴纸
const selectMark = (index: number) => {
    if (!originAvatarUrl.value) return ElMessage.warning('请先上传头像!')

    const markUrl = picList[styleIndex.value].markList[index]
    DrawRef.value.addMark(markUrl)
}

页面的交互逻辑相对简单,一步一步走就ok。

滚动通知动画效果

这里使用vue的过渡动画,模拟了滚动的效果, 本质就是key变了后,会触发弹入弹出效果。

<transition name="notice" mode="out-in">
    <div v-if="avatarList && avatarList.length" class="notice" :key="avatarList[noticeIndex].last_modified">
        <p>
            <span style="color: #409eff;">游客{{ (avatarList[noticeIndex].last_modified + '').slice(-5) }} </span>
            <span style="padding-left: 2px;">{{ calcOverTime(avatarList[noticeIndex].last_modified) }}前</span>
            <span style="padding-right: 2px;">制作了</span>
            <span style="color: #f56c6c;">{{ styleEnums[avatarList[noticeIndex].id] }}头像 </span>
            <span style="padding-left: 4px;"></span>
        </p>
        <img :src="avatarList[noticeIndex].url" alt="">
    </div>
</transition>

海报功能

这个用html2canvas库就好了,用正常的css属性,他都可以实现。

<!-- 生成海报 -->
<div id="poster" class="poster">
    <!-- 内容省略 -->
</div>
/* 注意图片跨域 */
await nextTick(() => {
    /* 生成海报 */
    const posterDom = document.getElementById('poster') as HTMLElement
    html2canvas(posterDom, { useCORS: true }).then((canvas) => {
        shareUrl.value = canvas.toDataURL('image/png')
        shareShow.value = true
        loading.value = false
    })
})

移动端瀑布流实现

pc和移动端都是grid布局,我们给移动端的行列份数随机,pc端强制设为1,保证行、列所占的份数一致就好(定制头像导出都是正方形的)

grid-auto-flow: dense; 这个样式是关键,

<div class="wall">
    <div class="wall-list">
        <el-image v-for="(url, index) in avatarPageUrlList" :key="url" :src="url" 
        :style="{ gridColumn: `span ${ avatarList[index].span}`, gridRow: `span ${ avatarList[index].span }` }" />
    </div>
</div>
.wall {
    .wall-list {
        display: grid;
        gap: 8px;
        grid-template-columns: repeat(8, minmax(0, 1fr));
        grid-auto-flow: dense;
    }

    .wall-more {
        padding-top: 16px;
        text-align: center;
    }
}

/* pc端不使用瀑布流,强覆盖行列份数 */
@media only screen and (min-width: 769px) {
    .wall {
        .wall-list {
            > div {
                grid-row: span 1 !important;
                grid-column: span 1 !important;
            }
        }
    }
}

到这里,基本核心、细节的点都实现了;若想知道更多代码设计、开发思路,请移步github,代码已开源。

本文转载于:

https://juejin.cn/post/7283018190594572328

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--Vue3 + Fabricjs 定制国庆专属头像文章来源地址https://www.toymoban.com/news/detail-710178.html

到了这里,关于记录--Vue3 + Fabricjs 定制国庆专属头像的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【定制小程序:开启你的专属数字化之旅】

    在当今数字化的时代,拥有一个定制的小程序已成为企业和个人展示个性、提升服务的必要手段。本文将为你详细介绍定制小程序开发的优势、流程以及如何选择合适的开发团队。 一、定制小程序开发的优势 个性化定制:根据你的需求和品牌特色,打造独一无二的小程序。

    2024年01月21日
    浏览(48)
  • ChatGPT神奇应用:定制化学习体验,get专属家教

    正文共  601  字,阅读大约需要  2  分钟 面向所有有学习需求的人群,您将在2分钟后获得以下超能力: 1、获取定制化学习体验 2、全面了解任何想学习的科目 Beezy评级 :B级 *经过简单的寻找, 大部分人能立刻掌握。主要节省时间。 推荐人  | Alice   编辑者  |  Linda ●此

    2024年02月11日
    浏览(46)
  • AnythingLLM:基于RAG方案构专属私有知识库(开源|高效|可定制)

    继OpenAI和Google的产品发布会之后,大模型的能力进化速度之快令人惊叹,然而,对于很多个人和企业而言,为了数据安全不得不考虑私有化部署方案,从GPT-4发布以来,国内外的大模型就拉开了很明显的差距,能够实现的此路径无非就只剩下国内的开源大模型可以选择了。而

    2024年02月04日
    浏览(60)
  • 超实用的 IPTV 管理工具,xTeVe 助你定制专属电视频道。

    虽然现在视频流媒体点播平台已经成为了大家主要的影音娱乐渠道,似乎没什么人看电视了,但我想需求还是在的,比如家里的长辈可能就不太会操作点播平台,他们比较习惯传统的直播电视,再比如新闻或者体育赛事,这类节目可能还是需要看直播,有时候放个电视节目当

    2024年01月17日
    浏览(69)
  • 想要定制专属AI声音?这是一份来自微软的保姆级攻略

    得益于AI技术的发展,合成声音已经能媲美人声。而声音定制服务的出现使得越来越多的企业和个人可以拥有个性化的独特AI声音。通常这种AI音色的定制需要采集人类配音员(发音人)的声音数据作为AI机器学习的对象,因此,个性化的声音定制又称为“声音复刻”,或者“

    2024年02月07日
    浏览(54)
  • 阿里首提前向训练框架:让大模型深度思考,可快速定制专属模型

    大语言模型(LLM)是当前自然语言处理领域最核心的技术,以 GPT-4 为代表的大语言模型展现出了类人的学习能力。其中,情境学习(In-context Learning)是大语言模型最神秘的能力之一。如下图所示,在这种情境学习的范式下,大模型无需更新任何参数,仅依赖几个示例样本(demonstrations)就可以学习新任务,执行新样本的预测。

    2024年02月11日
    浏览(45)
  • 微火:AI绘图网站程序源码搭建,定制专属的ai绘画小程序

    随着AI绘画的火热,群众对于AI绘画的需求与日俱增,目前已有的小程序、ai绘图软件已不能很好地满足当下用户的画图需求,经常排队生图,一排就是几个小时,或者前面直接8万人排队的现象早日屡见不鲜。 新的优秀的AI绘画小程序急待出炉。 做ai绘画程序,除了应对当下庞

    2024年02月02日
    浏览(44)
  • 宠物赛道,用AI定制宠物头像搞钱项目教程

    今天给大家介绍一个非常有趣,而粉丝价值又极高,用AI去定制宠物头像或合照的AI项目。 接触过宠物行业应该知道,获取1位铲屎官到私域,这类用户的价值是极高的,一个宠物粉,是连铲个屎都要花钱的,每年在宠物身上投入的钱在4-5位数。 假如她还愿给自己的宠物来做头

    2024年02月11日
    浏览(46)
  • 上传图片到腾讯云对象存储桶cos 【腾讯云对象存储桶】【cos】【el-upload】【vue3】【上传头像】【删除】

    1、首先登录腾讯云官网控制台 进入对象存储页面 2、找到跨越访问CIRS设置 配置规则  点击添加规则  填写信息  3、书写代码 这里用VUE3书写 第一种用按钮出发事件形式 4、测试 点击选择文件 选择图片  等待结果   第二种用el-upload 也可以把el-upload嵌套button包装成这种形式

    2024年02月15日
    浏览(69)
  • 【Vue】fabricjs 实现局部截图及el-image-viewer大图预览

    效果图: 再结合el-image-viewer可以实现大图预览局部图的效果。

    2024年02月22日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包