Canvas绘制毛玻璃背景分享海报

这篇具有很好参考价值的文章主要介绍了Canvas绘制毛玻璃背景分享海报。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近重新设计了分享海报,用毛玻璃作为背景,使整体更有质感,如果没有用到canvas,毛玻璃效果其实很好实现,给元素添加一个滤镜即可(比如:filter: blur(32px)),但是实践的过程中发现,canvas在IOS端一直没有效果,查了一个文档发现IOS端不支持filter。。。有点想骂人。。(PS:微信官方有关CanvasRenderingContext2D的文档还比较简略,更加详细的文档大家可以移步至https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)
Canvas绘制毛玻璃背景分享海报,前端开发,毛玻璃,开源项目,免费源码,小程序源码

没办法,实在是喜欢毛玻璃的效果,决定想想办法迈过这道坎,继续网上查阅资料,查到大概的方法就是

1、用canvas上下文先画一个image,image的src就是要模糊处理的图片路径,

2、用canvas上下文把步骤1的image提取imageData,然后进行高斯模糊,高斯模糊的算法网上有挺多,但是拿来执行运算时间要7,8s,比较不理想,最后我是找到了一个stackblur-canvas的插件,这个插件也适用于H5,这里只需要调用里面高斯模糊算法的方法(stackBlur.imageDataRGBA)即可,执行下来几百毫秒就能出来,跟网上的算法差距还是很明显啊。。

3、最后把已经模糊处理的imageData绘制到位图中即可。

canvas滤镜的问题搞定了,还有一个小问题是开发者工具上面canvas的层级始终会比其他元素高,但在IOS真机上元素是可以布局在canvas上。由于不知道在其他客户端会不会也会像开发者工具那样有层级显示问题。所以保险起见我干脆就把canvas放在在页面可视区域底部,在可视区域用其他页面元素做了一个分享效果图出来。

底下两张图,左边是预览,右边是最终画布生成图片。可以看出wxss的滤镜效果还是会比通过第三方高斯算法处理后的模糊效果来的更自然舒服,不过整体效果也还在可接受范围内。

Canvas绘制毛玻璃背景分享海报,前端开发,毛玻璃,开源项目,免费源码,小程序源码
Canvas绘制毛玻璃背景分享海报,前端开发,毛玻璃,开源项目,免费源码,小程序源码

附上核心代码

组件shareList.wxml

<view class="share-box" wx:if="{{visibility}}">
    <!-- style="display:none;"-->
    <view class="share-view" style="opacity:{{shareView?1:0}}">
        <view class="cover-image">
            <view class="cover"></view>
            <image class="image" src="{{shareInfo.imgSrc}}"></image>
        </view>
        <view class="content-box">
            <view class="detail">
                <view class="up">
                    <view class="expand">
                        <view class="time" wx:if="{{shareInfo.date}}">{{shareInfo.date}}</view>
                        <view class="place" wx:if="{{shareInfo.place}}">{{shareInfo.place}}</view>
                    </view>
                    <image mode="widthFix" class="image" src="{{shareInfo.imgSrc}}" bind:load="imageLoaded"></image>
                </view>
                <view class="middle flex-end-vertical">
                    <image class="header-img" src="{{shareInfo.avatarUrl}}"></image>
                    <image class="joiner-header-img" wx:if="{{shareInfo.joinerAvatarUrl}}" src="{{shareInfo.joinerAvatarUrl}}"></image>
                    <!--<view class="nickname flex-full">showms</view>-->
                </view>
                <view class="down">
                    <view class="desc"><view class="title" wx:if="{{shareInfo.title}}">#{{shareInfo.title}}#</view>{{shareInfo.content}}</view>
                </view>
            </view>
        </view>
    </view>
    <view class="save-tool-bar {{device.isPhoneX?'phx_68':''}} flex-center" style="transform: translateY({{shareView?0:100}}%)">
        <view class="op_btn flex-full">
            <view class="icon-view" bindtap="close">
                <image class="icon" src="/images/share/close.png"></image>
            </view>
            <text class="text" bindtap="close">关闭</text>
        </view>
        <view class="op_btn flex-full {{!allowSave?'disable':''}}">
            <view class="icon-view" bindtap="save">
                <image class="icon" src="/images/share/save.png"></image>
            </view>
            <text class="text" bindtap="save">保存到相册</text>
        </view>
    </view>
    <!--transform: translateY(100%);-->
    <canvas class="share-canvas"
            type="2d"
            id="myCanvas"
            style="position:absolute;top:0%;width:100%;height:100%;z-index:1000;transform: translateY(100%);"></canvas>
</view>
        <!--toast-->
<toast id="toast"></toast>

组件shareList.wxss

@import "/app.wxss";
.share-box{
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
}
.share-cover{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1200;
    background-color: #111;
    opacity: 0.8;
}
.share-view{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 300;
    opacity: 0;
    transition: opacity .3s;
}
.share-view .cover-image{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
}

.share-view .cover-image .image{
    width: 100%;
    height: 100%;
    transform: scale(3);
    filter: blur(32px);
}
.share-view .cover-image .cover{
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 120;
    background-color: rgba(255, 255, 255, .5);
}
.share-view .content-box{
    /*padding: 300rpx 40rpx 40rpx;
    position: relative;*/
    position: fixed;
    left: 40rpx;
    right: 40rpx;
    top: 50%;
    transform: translateY(-50%);
    margin-top: -80rpx;
    z-index: 500;
    box-sizing: border-box;
}
.share-view .content-box .detail{
    background-color: #fff;
    box-sizing: border-box;
    /*padding: 70rpx 30rpx 30rpx;*/
    border-radius: 36rpx;
    overflow: hidden;
}
.share-view .content-box .detail .up{
    box-sizing: border-box;
    position: relative;
}

.share-view .content-box .detail .up .image{
    width: 100%;
    /*height: auto;*/
    display: block;
}
.share-view .content-box .detail .up .expand{
    position: absolute;
    right: 20rpx;
    bottom: 20rpx;
    z-index: 10;
    text-align: right;
    font-size: 22rpx;
    font-weight: 500;
    color: #fff;
    text-shadow: 0px 0px 10rpx rgba(158, 163, 175, 1);
}
.share-view .content-box .detail .middle{
    position: relative;
}
.share-view .content-box .detail .middle .header-img,
.share-view .content-box .detail .middle .joiner-header-img{
    width: 100rpx;
    height: 100rpx;
    border-radius: 50%;
    border: 6rpx solid #fff;
    box-sizing: border-box;
    margin-left: 24rpx;
    margin-right: 10rpx;
    margin-top: -64rpx;
    position: relative;
    z-index: 80;
    display: block;
}
.share-view .content-box .detail .middle .joiner-header-img{
    z-index: 60;
    margin-left: -60rpx;
}
.share-view .content-box .detail .middle .nickname{
    font-size: 30rpx;
    font-weight: 500;
    color: #434343;
    padding-bottom: 10rpx;
    display: none;
}
.share-view .content-box .detail .down{
    padding: 10rpx 30rpx 70rpx;
}
.share-view .content-box .detail .down .title{
    font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    margin-bottom: 10rpx;
    margin-right: 10rpx;
    display: inline;
}
.share-view .content-box .detail .down .desc{
    /*font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    display: inline;*/
    font-size: 28rpx;
    font-weight: 500;
    color: #303133;
    line-clamp: 2;
    box-orient: vertical;

    text-overflow: ellipsis;
    overflow: hidden;
    /*将对象作为弹性伸缩盒子模型显示*/
    display: -webkit-box;
    /*从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)*/
    -webkit-box-orient: vertical;
    /*这个属性不是css的规范属性,需要组合上面两个属性,表示显示的行数*/
    -webkit-line-clamp: 2;

    height: 76rpx;
}
.share-canvas{

}
.save-tool-bar{
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1600;
    border-radius: 40rpx 40rpx 0 0;
    text-align: center;
    background-color: #f0f0f0;

    transform: translateY(100%);
    transition: transform .3s;
}
.save-tool-bar .op_btn{
    text-align: center;
    padding: 50rpx 0 20rpx;
    transition: opacity .3s;
}
.save-tool-bar .op_btn .icon-view{
    padding: 26rpx;
    background-color: #fff;
    border-radius: 50%;
    display: inline-block;
    margin-bottom: 10rpx;
}
.save-tool-bar .op_btn .icon{
    display: block;
    width: 48rpx;
    height: 48rpx;
}
.save-tool-bar .op_btn .text{
    display: block;
    font-size: 20rpx;
    font-weight: 400;
}

组件shareList.js

//获取应用实例
const app = getApp();
const tabbar = require('../../utils/tabbar.js');
const canvasHelper = require('../../utils/canvasHelper');
const fonts = require("../../utils/fonts.js");

let ctx, canvas;
Component({
    /**
     * 组件的属性列表
     */
    properties: {},

    /**
     * 组件的初始数据
     */
    /*{left: 'rgba(26, 152, 252, 0.8)', right: 'rgba(26, 152, 252, 1)'}*/
    data: {
        visibility: false,
        paddingLeft: 34,
        letterSpace: 2,
        width: 300,
        height: 380,
        shareView: false,
        shareInfo: {
            /*imgSrc: "cloud://ydw-49d951.7964-ydw-49d951-1259010930/love/images/default/dangao.jpg",
            avatarUrl: "cloud://test-wjaep.7465-test-wjaep-1259010930/images/ozzW05Gch7jMMhsn1r_SWLGdGtF0/avatarUrl_1668440942555.webp",
            joinerAvatarUrl: "
https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83erg8t0El6jTaZY87icvjR71ww52VMibg8fONBgggRtYHTnR2tXibB0IRQ45dCVgNCX5BRhY0KibjfxjGA/132
",
            title: "一起去旅游",
            content: "这里是内容啊,怎么没写内容呢这里是内容啊,怎么没写内容呢,我特知道当时的回复对方法国发过快速减肥",
            date: "2022-12-28",
            place: "四川成都",
            shareQrcode: "cloud://ydw-49d951.7964-ydw-49d951-1259010930/qrcode/mini-qrcode.jpg"*/
        },
        allowSave: false,
    },

    ready() {
        const device = app.getSystemInfo();
        this.setData({
            device,
        });
        this.toast = this.selectComponent('#toast');
    },

    /**
     * 组件的方法列表
     */
    methods: {
        save() {
            const {device} = this.data;
            let that = this;
            that.toast.showLoadingToast({text: "保存中", mask: false});

            canvasHelper.saveImage(
                this,
                canvas,
                0,
                0,
                device.screenWidth,
                device.screenHeight,
                device.screenWidth * 5,
                device.screenHeight * 5
            ).then(res => {
                that.toast.hideLoadingToast();
                that.toast.showToast({text: "保存成功"});
                that.close();
                console.log(res);
            }).catch(res => {
                console.error("保存失败:", JSON.stringify(res));
                that.toast.showToast({text: "保存失败"});
                that.toast.hideLoadingToast();
            });
        },

        imageLoaded: function (e) {
            console.log("图片加载完毕:", e);
            this.setData({shareView: true});
        },

        show(shareInfo) {
            console.log("开始显示画布:", shareInfo);
            this.setData({
                shareInfo
            });
            tabbar.hideTab(this);
            this.setData({visibility: true}, () => {
                canvasHelper.init(app, this, "#myCanvas").then(async (res) => {
                    ctx = res.ctx;
                    canvas = res.canvas;

                    this.toast.showLoadingToast({text: "生成中", mask: false});
                    const {device} = this.data;
                    //加大尺寸
                    const largerSize = 100;
                    console.log("1.绘制毛玻璃背景图片:", {
                        width: device.screenHeight + largerSize,
                        height: device.screenHeight + largerSize,
                    });

                    /*await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        -(device.screenHeight - device.screenWidth) / 2.0, -largerSize / 2,
                        device.screenHeight + largerSize,
                        device.screenHeight + largerSize,
                        190);*/

                    await canvasHelper.drawBlurImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        -(device.screenHeight - device.screenWidth) / 2.0, 0,
                        device.screenHeight,
                        device.screenHeight,
                        180);

                    console.log("2.绘制毛玻璃覆盖层灰色背景");
                    canvasHelper.drawRoundRect(ctx, 0, 0, device.screenWidth, device.screenHeight, 0, 'rgba(255, 255, 255, .5)');

                    console.log("3.绘制内容承载区域");
                    const leftPadding = 20,//边距20
                        headerImgHeight = 50,//头像尺寸
                        descHeight = 40,//内容区域高度
                        descPaddingTop = 0,//内容区域paddingTop
                        descPaddingBottom = 25,//内容区域paddingBottom
                        adjustHeight = 40;//调节高度,人为设定

                    const contentWidth = device.screenWidth - leftPadding * 2;
                    const contentHeight = contentWidth + headerImgHeight + descHeight + descPaddingTop + descPaddingBottom;
                    canvasHelper.drawRoundRect(
                        ctx,
                        (device.screenWidth - contentWidth) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 - adjustHeight,
                        contentWidth,
                        contentHeight,
                        18,
                        'rgba(255, 255, 255, 1)'
                    );

                    console.log("4.绘制内容区域图片");
                    ctx.clip();//裁剪后父元素的圆角才会显示
                    await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.imgSrc,
                        (device.screenWidth - contentWidth) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 - adjustHeight,
                        contentWidth,
                        contentWidth,
                        0,
                    );
                    ctx.restore();

                    console.log("5.绘制头像边框");
                    const headerSize = 50, borderWidth = 3, headerMarginLeft = 12;

                    if (shareInfo.joinerAvatarUrl) {
                        console.log("5.1.绘制共享对象头像");
                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.joinerAvatarUrl,
                            leftPadding + headerMarginLeft + 30,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                        console.log("5.2.绘制当前用户头像");

                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.avatarUrl,
                            leftPadding + headerMarginLeft,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                    } else {
                        console.log("5.1.绘制当前用户头像");
                        await canvasHelper.drawCircleImage(
                            canvas,
                            ctx,
                            shareInfo.avatarUrl,
                            leftPadding + headerMarginLeft,
                            (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2,
                            headerSize,
                            borderWidth,
                            "#fff",
                        );
                    }

                    console.log("6.绘制日期和地点");
                    let textPositionY = (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth - headerSize / 2 + 14;
                    if (shareInfo.place) {
                        console.log("6.1.绘制地点");
                        canvasHelper.drawText(
                            ctx,
                            shareInfo.place,
                            leftPadding + contentWidth - 10,
                            textPositionY,
                            "right",
                            11,
                            400,
                            fonts.list["SFRounded-Regular"].name,
                            "#fff",
                            "rgba(158, 163, 175, 1)",
                            0,
                            0,
                            5);

                        textPositionY = textPositionY - 16
                    }
                    if (shareInfo.date) {
                        console.log("6.2.绘制日期");
                        canvasHelper.drawText(
                            ctx,
                            shareInfo.date,
                            leftPadding + contentWidth - 10,
                            textPositionY,
                            "right",
                            11,
                            400,
                            fonts.list["SFRounded-Regular"].name,
                            "#fff",
                            "rgba(158, 163, 175, 1)",
                            0,
                            0,
                            5);
                    }

                    if (shareInfo.title || shareInfo.desc) {
                        console.log("7.绘制标题和内容", contentWidth);
                        let leftContent = (shareInfo.title ? "#" + shareInfo.title + "#  " : "") + shareInfo.content;
                        //显示区域宽度
                        const displayWidth = contentWidth - 40;
                        textPositionY = (device.screenHeight - contentHeight) / 2.0 - adjustHeight + contentWidth + headerSize;
                        const contentFontSize = 14, contentFontWeight = 600;
                        for (let i = 1; i <= 2; i++) {
                            let dynamicText = "";
                            for (let j = 0; j < leftContent.length;) {
                                ctx.font = contentFontWeight + " " + contentFontSize + "px " + fonts.list["SFRounded-Semibold"].name;
                                let metrics = ctx.measureText(dynamicText + leftContent[j]);
                                if (metrics.width >= displayWidth) {
                                    //最后一行,最后一个字替换成省略号
                                    if (i === 2) {
                                        dynamicText = dynamicText.substring(0, dynamicText.length - 1);
                                        dynamicText += "…"
                                    }
                                    break;
                                }
                                dynamicText += leftContent[j];
                                leftContent = leftContent.slice(1);
                            }
                            //console.log("文本内容:", dynamicText);
                            canvasHelper.drawText(
                                ctx,
                                dynamicText,
                                leftPadding + 20,
                                textPositionY,
                                "left",
                                contentFontSize,
                                contentFontWeight,
                                fonts.list["SFRounded-Semibold"].name,
                                "#303133",
                                "",
                                0,
                                0,
                                0
                            );
                            textPositionY = textPositionY + 20;
                        }
                    }

                    console.log("8.绘制二维码");
                    const qrcodeSize = 66, qrcodeHolderSize = 70;

                    canvasHelper.drawRoundRect(
                        ctx,
                        (device.screenWidth - qrcodeHolderSize) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 + contentHeight,
                        qrcodeHolderSize,
                        qrcodeHolderSize,
                        10,
                        'rgba(255, 255, 255, 1)'
                    );
                    ctx.clip();
                    await canvasHelper.drawImage(
                        canvas,
                        ctx,
                        shareInfo.shareQrcode,
                        (device.screenWidth - qrcodeSize) / 2.0,
                        (device.screenHeight - contentHeight) / 2.0 + contentHeight + 2,
                        qrcodeSize,
                        qrcodeSize,
                        0,
                    );
                    ctx.restore();

                    /** await canvasHelper.drawCircleImage(
                     canvas,
                     ctx,
                     shareInfo.shareQrcode,
                     (device.screenWidth - qrcodeSize) / 2.0,
                     (device.screenHeight - contentHeight) / 2.0 + contentHeight,
                     qrcodeSize,
                     0
                     );*/

                    ctx.fill();
                    this.setData({allowSave: true});
                    this.toast.hideLoadingToast();
                });

            });
        },

        /**
         * 下载头像
         * @param avatarUrl
         * @returns {Promise}
         * @private
         */
        _downloadHeaderImg(avatarUrl) {
            return new Promise(((resolve, reject) => {
                wx.downloadFile({
                    url: avatarUrl, //下载头像
                    success(res) {
                        console.log("下载结束:", res);
                        if (res.statusCode === 200) {
                            //res.tempFilePath
                            resolve(res);
                        }
                    }, fail(res) {
                        reject(res);
                    }
                });
            }));
        },

        /**
         * 关闭分享页面
         */
        close() {
            this.toast.hideLoadingToast();
            this.setData({visibility: false, shareView: false, allowSave: false});
            tabbar.showTab(this);
        },
    }
})
;

工具类canvasHelper

const stackBlur = require("stackblur-canvas");

/**
 * 初始化画布
 * @param app
 * @param base
 * @param canvasId
 * @returns {Promise}
 */
const init = (app, base, canvasId) => {
    return new Promise((resolve, reject) => {
        const device = app.getSystemInfo();
        const query = base.createSelectorQuery();
        query.select(canvasId)
            .fields({node: true, size: true})
            .exec((res) => {
                const canvas = res[0].node;
                const ctx = canvas.getContext('2d');
                const dpr = device.pixelRatio;
                canvas.width = res[0].width * dpr;
                canvas.height = res[0].height * dpr;
                ctx.scale(dpr, dpr);
                console.log("画布初始化完毕,画布宽:", canvas.width, "画布高:", canvas.height, "设备像素比:", dpr);
                resolve({ctx, canvas});
            });
    });
}

/**
 * 绘制圆角矩形
 * @param ctx
 * @param {number} x 圆角矩形选区的左上角 x坐标
 * @param {number} y 圆角矩形选区的左上角 y坐标
 * @param {number} w 圆角矩形选区的宽度
 * @param {number} h 圆角矩形选区的高度
 * @param {number} r 圆角的半径
 * @param {string} f 填充颜色
 */
const drawRoundRect = (ctx, x, y, w, h, r, f) => {
    ctx.save();
    // 开始绘制
    ctx.beginPath();
    // 因为边缘描边存在锯齿,最好指定使用 transparent 填充
    // 这里是使用 fill 还是 stroke都可以,二选一即可
    ctx.fillStyle = f;
    // ctx.setStrokeStyle('transparent')
    // 左上角
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);

    // border-top
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.lineTo(x + w, y + r);
    // 右上角
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);

    // border-right
    ctx.lineTo(x + w, y + h - r);
    ctx.lineTo(x + w - r, y + h);
    // 右下角
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);

    // border-bottom
    ctx.lineTo(x + r, y + h);
    ctx.lineTo(x, y + h - r);
    // 左下角
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);

    // border-left
    ctx.lineTo(x, y + r);
    ctx.lineTo(x + r, y);

    // 这里是使用 fill 还是 stroke都可以,二选一即可,但是需要与上面对应
    ctx.fill();
    // ctx.stroke()
    ctx.closePath();
    // 剪切
    //ctx.clip();
};

/**
 * 绘制圆形
 * @param ctx
 * @param circlePointX
 * @param circlePointY
 * @param radius
 * @param backgroundColor
 * @param save
 */
const drawCircle = (ctx, circlePointX, circlePointY, radius, backgroundColor, save = true) => {
    if (save) ctx.save();
    //ctx.save();
    ctx.beginPath();
    ctx.arc(circlePointX, circlePointY, radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = backgroundColor;
    ctx.fill();
    ctx.clip();
    //ctx.restore();
    if (save) ctx.restore();
};

/**
 * 绘制圆形白边图像
 * @param canvas
 * @param ctx 图像
 * @param imageUrl 图像
 * @param startPositionX x坐标
 * @param startPositionY y坐标
 * @param size 图像大小,除以2就是圆半径
 * @param borderWidth 边框宽度
 * @param borderColor 边框颜色
 * @returns {Promise<*>}
 * @private
 */
const drawCircleImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, size, borderWidth, borderColor) => {
    return new Promise((resolve, reject) => {
        //叠加圆形的绘制,需要先保存一下原始环境,然后绘制完第一个圆形后恢复绘制环境,即ctx.restore();
        ctx.save();
        drawCircle(
            ctx,
            startPositionX + size / 2,
            startPositionY + size / 2,
            size / 2,
            borderColor, false
        );

        if (borderWidth) {
            drawCircle(
                ctx,
                startPositionX + size / 2,
                startPositionY + size / 2,
                size / 2 - borderWidth,
                borderColor, false
            );
        }
        drawImage(
            canvas,
            ctx,
            imageUrl,
            startPositionX,
            startPositionY,
            size,
            size,
            0,
        ).then(res => {
            ctx.restore();
            resolve(res);
        }).catch(res => {
            reject(res);
        });
    });

};

/**
 * 绘制高斯模糊效果的图像
 * @param canvas
 * @param ctx
 * @param imageUrl
 * @param startPositionX
 * @param startPositionY
 * @param width
 * @param height
 * @param blur
 * @returns {Promise}
 * @private
 */
const drawBlurImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, width, height, blur) => {
    return new Promise((resolve, reject) => {
        wx.getImageInfo({
            src: imageUrl,//服务器返回的图片地址
            success: function (res) {
                console.log("=>", res);
                let imgObj = canvas.createImage();
                imgObj.src = res.path;
                imgObj.onload = async function (e) {
                    console.log("=========>", imgObj.width, imgObj.height)
                    ctx.save();
                    ctx.beginPath();
                    ctx.drawImage(imgObj, startPositionX, startPositionY, width, height);
                    //提取图片信息
                    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

                    //进行高斯模糊
                    let gd = stackBlur.imageDataRGBA(imageData, 0, 0, canvas.width, canvas.height, blur);

                    //绘制模糊图像
                    ctx.putImageData(gd, 0, 0)

                    ctx.restore();
                    console.log("图片加载完毕:", e);
                    resolve();
                };
            },
            fail: function (res) {
                console.log(res);
                reject(res);
            }
        });
    })
};

/**
 * 绘制文本
 * @param ctx
 * @param text
 * @param startPositionX
 * @param startPositionY
 * @param textAlign
 * @param fontSize
 * @param fontWeight
 * @param color
 * @param shadowColor 阴影颜色
 * @param shadowOffsetX 阴影水平偏移距离
 * @param shadowOffsetY 阴影垂直偏移距离
 * @param shadowBlur 模糊程度
 * @param letterSpace 间隔
 * @returns {number}
 * @private
 */
const drawText = (ctx, text, startPositionX, startPositionY, textAlign, fontSize, fontWeight, fontFamily, color, shadowColor, shadowOffsetX, shadowOffsetY, shadowBlur, letterSpace = 0) => {
    if (!text) {
        return 0;
    }
    let textWidth = 0;
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = color;
    ctx.textAlign = textAlign;
    ctx.font = fontWeight + " " + fontSize + "px " + fontFamily;

    if (shadowColor) {
        console.log("设置阴影:", shadowColor);
        // 设置阴影
        ctx.shadowColor = shadowColor; //阴影颜色
        ctx.shadowOffsetX = shadowOffsetX; //偏移
        ctx.shadowOffsetY = shadowOffsetY;
        ctx.shadowBlur = shadowBlur; //模糊程度
    }

    if (!letterSpace) {
        let metrics = ctx.measureText(text);
        console.log("文字[" + text + "]宽度:", metrics.width);
        ctx.fillText(
            text,
            startPositionX,
            startPositionY
        );
        textWidth = metrics.width;
    } else {
        //对齐方式调整为left
        ctx.textAlign = "left";
        let positionXArr = [];//坐标集合
        textWidth = ctx.measureText(text).width + (text.length - 1) * letterSpace;//含letterSpace的文字总宽度
        for (let i = 0; i < text.length; i++) {
            if (i === 0) {
                switch (textAlign) {
                    case "left":
                        positionXArr.push(startPositionX);
                        break;
                    case "center":
                        positionXArr.push(startPositionX - textWidth / 2);
                        break;
                    case "right":
                        positionXArr.push(startPositionX - textWidth);
                        break;
                    default:
                        console.warn("暂不支持的textAlign:", textAlign);
                        break;
                }
            } else {
                let metrics = ctx.measureText(text[i - 1]);
                positionXArr.push(positionXArr[i - 1] + metrics.width + letterSpace);
            }
        }
        for (let i = 0; i < text.length; i++) {
            ctx.fillText(
                text[i],
                positionXArr[i],
                startPositionY
            );
        }
    }

    ctx.restore();
    return textWidth;
};

/**
 * 绘制图图像
 * @param canvas
 * @param ctx
 * @param startPositionX
 * @param startPositionY
 * @param width
 * @param height
 * @param blur
 * @param imageUrl
 * @returns {Promise}
 * @private
 */
const drawImage = (canvas, ctx, imageUrl, startPositionX, startPositionY, width, height, blur) => {
    return new Promise((resolve, reject) => {
        wx.getImageInfo({
            src: imageUrl,//服务器返回的图片地址
            success: function (res) {
                console.log("=>", res);
                let imgObj = canvas.createImage();
                imgObj.src = res.path;
                imgObj.onload = function (e) {
                    ctx.save();
                    ctx.beginPath();
                    ctx.filter = 'blur(' + blur + 'px)';
                    // ctx.globalAlpha = 0.6
                    ctx.drawImage(imgObj, startPositionX, startPositionY, width, height);
                    ctx.restore();
                    console.log("图片加载完毕:", e);
                    resolve(res);
                };
            },
            fail: function (res) {
                console.log(res);
                reject(res);
            }
        });
    })

};

/**
 * 将画布内容保存为图片
 * @param base
 * @param canvas
 * @param x
 * @param y
 * @param width
 * @param height
 * @param destWidth
 * @param destHeight
 * @param quality
 * @param fileType
 * @returns {Promise}
 */
const saveImage = (base, canvas, x, y, width, height, destWidth, destHeight, quality = 1, fileType = "png") => {
    return new Promise((resolve, reject) => {
        wx.canvasToTempFilePath({
            x,
            y,
            width,
            height,
            destWidth,
            destHeight,
            quality,
            fileType,
            canvas,
            success(res) {
                console.log(res.tempFilePath);
                wx.saveImageToPhotosAlbum({
                    filePath: res.tempFilePath,
                    success: (res) => {
                        resolve(res);
                    },
                    fail: (err) => {
                        console.error(err)
                        reject(err);
                    }
                })
            },
            fail(res) {
                console.error("保存失败:", JSON.stringify(res));
                reject(res);
            }
        }, base)
    });

}

module.exports = {
    init, drawCircle, drawCircleImage, drawImage, drawBlurImage, drawText, drawRoundRect, saveImage
}

学习更多技术开发知识请关注CRMEB开源商城文章来源地址https://www.toymoban.com/news/detail-661870.html

到了这里,关于Canvas绘制毛玻璃背景分享海报的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 毛玻璃态卡片悬停效果

    页面的组成部分主要是卡片。其中卡片的组成部分主要是包括了图片和详情。 卡片的动效是鼠标悬停在卡片上时,图片会移动到左侧,并且图片是毛玻璃效果。所以我们在布局的时候图片会采用绝对布局。而详情则是基础布局。 响应式 绝对布局 filte 属性的 invert 值使用 ba

    2024年02月07日
    浏览(45)
  • react native 毛玻璃效果

    https://docs.expo.dev/versions/latest/sdk/blur-view/ https://github.com/expo/expo/issues/6613     参考链接: https://chat.xutongbao.top/

    2024年02月09日
    浏览(43)
  • Unity CommandBuffer实现毛玻璃特效

    特效可以用于一般物体,也可以应用于UI。一直想在UI中实现这么个效果,现在终于实现了。 效果如下: 应用于场景中的屏物体 应用于UI 调节Shader的参数,可以调整背景模糊和扭曲的程度,也可以调整毛玻璃的颜色,还可也换法线和纹理贴图。 原理 用到是 CommandBuffer ,其实

    2024年02月16日
    浏览(45)
  • 3D 毛玻璃晶质见证卡

    从上面的效果展示来看,页面主要成员是一张卡片,并且卡片上有三个小矩形,而小矩形上会展示对应的内容。 当鼠标悬停在卡片上时,卡片会随着鼠标的移动而改变视角。 transform-style 属性的 preserve-3d 值运用 transform 属性的 translateZ 值运用 VanillaTilt.js 与 translateZ 的 3D 视角

    2024年02月06日
    浏览(40)
  • HTML 和 CSS 来实现毛玻璃效果(Glassmorphism)

            它的主要特征就是半透明的背景,以及阴影和边框。 同时还要为背景加上模糊效果,使得背景之后的元素根据自身内容产生漂亮的“变形”效果,示例: 首先,创建一个 HTML 文件,写入如下内容: 为  body  标签添加一些样式,并使用鲜艳的颜色和渐变作为背景

    2024年02月11日
    浏览(41)
  • CSS:backdrop-filter实现毛玻璃的效果

    实现效果 实现代码 完整代码 前端笔记 - 【CSS】 - filter 于 backdrop-filter

    2024年01月21日
    浏览(39)
  • OpenCV轻松入门(六)——简单图片处理【马赛克、毛玻璃、浮雕效果】

    马赛克效果 马赛克指现行广为使用的一种图像(视频)处理手段,此手段将影像特定区域的色阶细节劣化并造成色块打乱的效果,因为这种模糊看上去有一个个的小格子组成,便形象的称这种画面为马赛克。其目的通常是使之无法辨认。 下面,我们来介绍一下实现马赛克的思

    2024年04月14日
    浏览(43)
  • UI小姐姐说我用CSS实现毛玻璃效果的样子很帅

    123123123123123123123123123123123123123123123123123123123123123123123123123123123123 模糊了吗?确实模糊了。但是有毛玻璃效果吗?没有,毛都没有。我们看下 用backdrop-filter是什么效果的。 .card { margin: 100px auto; width: 300px; height: 300px; position: relative; border: 1px solid #000; color: white; backdrop-f

    2024年04月12日
    浏览(36)
  • 在微信小程序中或UniApp中自定义tabbar实现毛玻璃高斯模糊效果

    backdrop-filter: blur(10px); 这一行代码表示将背景进行模糊处理,模糊程度为10像素。这会导致背景内容在这个元素后面呈现模糊效果。 background-color: rgb(255 255 255 / .32); 这一行代码表示设置元素的背景颜色为白色(RGB值为0, 0, 0),并且通过/符号后面的透明度值(32%不透明度)使背

    2024年04月09日
    浏览(47)
  • 在H5、微信小程序中使用canvas绘制二维码、分享海报

    提示:绘制二维码的插件有很多,有些仅支持H5,有些只适用微信小程序,故读者在使用二维码插件前需要先查看插件官方文档,查看其支持的环境 H5中安装qrious插件 引入qrious canvas模板 初始化canvas 二维码绘制并渲染 微信小程序中安装weapp-qrcode插件 引入weapp-qrcode canvas模板

    2024年02月14日
    浏览(57)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包