node.js用户上传图片选择相框生成照片保存

一、效果预览

用户选择本地图片,上传后,从下方选择相框。并在相框中缩放调整图片。确认后即可生成照片,图片长按即可保存。

 二、node.js部分

图片上传部分,使用express和multer。注意不要使用跳转方式,不然无法上传成功。

var express = require("express");
var multer = require("multer");
var compression = require('compression');
var storage = multer.diskStorage({
    destination: function(req, file, cb) {
        cb(null, './dist/uploads');
    },
    filename: function(req, file, cb) {
        cb(null, `${file.originalname}`)
    }
})
var upload = multer({ storage: storage });
var app = express();
app.use(express.static('./dist'));
app.post('/upimages', upload.array('imgfile', 40), function(req, res, next) {
    var files = req.files;
    if (!files[0]) {
        res.send('error');
    } else {
        res.send('success');
    }
});

app.use(compression());
app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
    next();
});

var server = app.listen(8054, function() {
    console.log('server is running at port 8054...');
});

 三、前端部分

首先是对图片的位置进行处理,让图片自适应相框的位置,部分参数需要在PS里手动测量像素数量。

上传图片后,使用PhotoClip对照片进行裁剪,还使用了图片缩放功能,相框选择使用Swiper,使用canvas生成图片,实现图片上传。

使用了较多js库,参见html页面。

//获取URL参数
function getQueryVariable(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (pair[0] == variable) { return pair[1]; }
    }
    return (false);
}
var imgID = getQueryVariable("img");
if (imgID) {
    $("title").html("我的专属纪念地图");
    $(".page3").show();
    var imgName = './uploads/' + imgID;
    $('#daiyan_bg1').attr('src', imgName);
    var tipTop = $(window).width() * 1866 / 1276 / 2 - 40;
    $(".save_tip").show().css("top", tipTop);
    $(".save_tip").css("opacity", '0.6');
    setTimeout(function() {
        $(".save_tip").hide();
    }, 8000);
} else {
    $("title").html("专属定制");
    $(".page1").show();
}

var fontSizeStr = document.documentElement.style.fontSize;
var fontSize = parseFloat(fontSizeStr.substring(0, fontSizeStr.length - 2));

//url转blob
function dataURLToBlob(dataurl) {
    let arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {
        type: mime
    });
}

$(document).ready(function() {
    var clipImg, frameID = 0,
        imgSrc;
    var screenWidth = $(window).width();
    var screenHeight = $(window).height();
    var maskWidth = 1276;
    var maskHeight = 1866;
    var sizeRate = maskHeight / maskWidth;

    var realBgWidth = screenWidth;
    var realBgHeight = screenHeight - 4.2 * fontSize;
    var isLong = (realBgHeight > realBgWidth * sizeRate);

    var bgWidth = isLong ? realBgWidth : realBgHeight / sizeRate;
    var bgHeight = isLong ? realBgWidth * sizeRate : realBgHeight;
    var topMargin = (realBgHeight - bgHeight) / 2;
    var leftMargin = (realBgWidth - bgWidth) / 2;

    var frameHeight = realBgHeight.toString() + "px";
    var canvasHeight = (screenWidth * sizeRate).toString() + "px";
    var imgWidth = bgWidth.toString() + "px";
    var imgHeight = bgHeight.toString() + "px";
    var imageTop = topMargin.toString() + "px";
    var imageLeft = leftMargin.toString() + "px";

    $('.daiyan0').css("height", frameHeight);
    $('.daiyan0').css("width", "100%");
    $('.daiyan0 .qrcode').css("height", imgHeight);
    $('.daiyan0 .qrcode').css("width", imgWidth);
    $('.daiyan0 .qrcode').css("top", imageTop);
    $('.daiyan0 .qrcode').css("left", imageLeft);
    
    //边框距离
    var sizeList = [{
        margin: [370 / maskHeight, 110 / maskWidth, 1010 / maskHeight, 410 / maskWidth]
    }, {
        margin: [120 / maskHeight, 80 / maskWidth, 1180 / maskHeight, 630 / maskWidth]
    }, {
        margin: [280 / maskHeight, 215 / maskWidth, 1080 / maskHeight, 215 / maskWidth]
    }];

    var pcWidth = function(i) {
        return bgWidth * (1 - sizeList[i].margin[1] - sizeList[i].margin[3]);
    };
    var pcHeight = function(i) {
        return bgHeight * (1 - sizeList[i].margin[0] - sizeList[i].margin[2]);
    };
    var frameLeft = function(i) {
        return maskWidth * sizeList[i].margin[3];
    };
    var frameTop = function(i) {
        return maskHeight * sizeList[i].margin[0];
    };
    var photoWidth = function(i) {
        return maskWidth * (1 - sizeList[i].margin[1] - sizeList[i].margin[3]);
    };
    var photoHeight = function(i) {
        return maskHeight * (1 - sizeList[i].margin[0] - sizeList[i].margin[2]);
    };
    var clipWidth = function(i) {
        return pcWidth(i).toString() + "px";
    };
    var clipHeight = function(i) {
        return pcHeight(i).toString() + "px";
    };
    var clipMargin = function(i) {
        return (bgHeight * sizeList[i].margin[0] + topMargin).toString() + "px " + (bgWidth * sizeList[i].margin[1] + leftMargin).toString() + "px " + (bgHeight * sizeList[i].margin[2] + topMargin).toString() + "px " + (bgWidth * sizeList[i].margin[3] + leftMargin).toString() + "px";
    };

    $('.daiyan1').css("height", clipHeight(0));
    $('.daiyan1').css("width", clipWidth(0));
    $('.daiyan1').css("margin", clipMargin(0));

    $('#daiyan_bg1').css("height", canvasHeight);
    
    //裁剪上传的照片
    var pc = new PhotoClip('.page2 .daiyan1', {
        size: [pcWidth(0), pcHeight(0)],
        file: '.uploadfile',
        outputQuality: 1,
        maxZoom: 2,
        ok: '.save',
        loadStart: function() {
            console.log('开始读取照片');
        },
        loadComplete: function() {
            console.log('照片读取完成');
            $(".page1").hide().siblings(".page2").show();
            $('.photo-clip-mask').css("display", 'none');

            var swiperCount = (screenWidth / (fontSize * 2.5 / sizeRate)) - 0.2;
            var swiper = new Swiper('.swiper-container', {
                slidesPerView: 3,
                spaceBetween: 10,
                on: {
                },
            });
            swiper.slides.each(function(index, val) {
                var ele = $(this);
                ele.addClass("opacity");
                ele.on("click", function() {
                    var index = $(this).index();
                    imgSrc = './images/frame_' + (index + 1).toString() + '.png';
                    $('.qrcode').attr('src', imgSrc);
                    $('.swiper-slide').addClass("opacity");
                    $(this).removeClass("opacity");
                    $('.daiyan1').css("height", clipHeight(index));
                    $('.daiyan1').css("width", clipWidth(index));
                    $('.daiyan1').css("margin", clipMargin(index));
                    pc.size(pcWidth(index), pcHeight(index));
                    frameID = index;
                });
            });
            $('.swiper-slide-active').removeClass("opacity");
        },
        done: function(dataURL) {
            clipImg = dataURL;
        },
        fail: function(msg) {
            alert(msg);
        }
    });

    // 加载的图片必须要与本程序同源,否则无法截图
    $(".prev").click(function() {
        window.location.href = "?"; //"?random=" + Date.parse(new Date());
        formatTransform(0, 0);
    });

    $(".share").click(function() {
        $(".alert").show();
        $(".save_tip").hide();
    });

    $(".alert").click(function() {
        $(".alert").hide();
    });

    function formatTransform(offx, offy) {
        var translate = 'translate3d(' + (offx + 'px,') + (offy + 'px,') + '0)';
        return translate;
    }

    $(".save").click(function() {
        var tipTop = screenWidth * sizeRate / 2;
        $(".load_tip").show().css("top", tipTop);
        $(".load_tip").show();
        var imgbox = document.getElementById("daiyan_bg"),
            canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        canvas.width = maskWidth;
        canvas.height = maskHeight;

        var imgUrl = new Image,
            qrCodeUrl = new Image;
        qrCodeUrl.src = './images/frame_' + (frameID + 1).toString() + '.png';
        qrCodeUrl.onload = function() {
            imgUrl.crossOrigin = "anonymous";
            imgUrl.src = clipImg;
        };

        imgUrl.onload = function() {
            $(".load_tip").hide();

            ctx.fillStyle = "#fff";
            ctx.fillRect(0, 0, maskWidth, maskHeight);

            ctx.beginPath();
            ctx.drawImage(imgUrl, 0, 0, this.width, this.height, frameLeft(frameID), frameTop(frameID), photoWidth(frameID), photoHeight(frameID));
            ctx.drawImage(qrCodeUrl, 0, 0, maskWidth, maskHeight, 0, 0, maskWidth, maskHeight);


            var xhr = new XMLHttpRequest();
            var formdata = new FormData();
            var dataurl = canvas.toDataURL('image/jpeg');
            var blobImg = new Blob();
            blobImg = dataURLToBlob(dataurl);
            var imgName = Date.now() + '.jpg';

            var progress = document.querySelector('progress');
            formdata.append('imgfile', blobImg, imgName);

            xhr.open('POST', '/upimages');
            xhr.onload = () => {
                console.log(xhr);
                if (xhr.status === 200 && xhr.responseText === 'success') {
                    xhr = null;
                    console.log('图片上传成功!');
                    window.location.href = "?img=" + imgName;
                }
            }
            xhr.send(formdata);
        };
    });
});
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <title></title>
    <script src="js/jquery-3.5.1.min.js"></script>
    <script>
        //计算根节点HTML的字体大小
        function resizeRoot(width) {
            var deviceWidth = document.documentElement.clientWidth,
                num = width,
                num1 = num / 100;
            if (deviceWidth > num) {
                deviceWidth = num;
            }
            document.documentElement.style.fontSize = deviceWidth / num1 + "px";
        }
        //根节点HTML的字体大小初始化
        resizeRoot(750);
        window.onresize = function() {
            resizeRoot(750);
        };
    </script>
    <link rel="stylesheet" href="css/swiper-bundle.min.css">
    <link rel="stylesheet" href="css/index.css">
</head>

<body>
    <section class="page1">
        <div class="daiyan">
            <p class="play"><input type="file" accept="image/*" class="uploadfile"></p>
        </div>
    </section>
    <section class="page2">
        <div class="daiyan daiyan0"><img id="qrcode" src="images/frame_1.png" alt="" class="qrcode"></div>
        <div class="daiyan daiyan1"></div>
        <div class="swiper-container">
            <div class="swiper-wrapper">
                <div class="swiper-slide"><img src="images/frame_1.png"></div>
                <div class="swiper-slide"><img src="images/frame_2.png"></div>
                <div class="swiper-slide"><img src="images/frame_3.png"></div>
            </div>
        </div>
        <img src="images/load.png" alt="" class="load_tip">
        <div class="daiyan_ctrl"><button class="prev">重新选择</button><button class="save">点击生成</button></div>
    </section>
    <section class="page3">
        <div class="daiyan daiyan2">
            <img id="daiyan_bg1" src="" alt="" class="daiyan_bg" style="width: 100%;opacity: 1;">
        </div>
        <img src="images/save.png" alt="" class="save_tip">
        <div class="daiyan_ctrl"><button class="prev">我也试试</button><button class="share">点击分享</button></div>
    </section><canvas id="myCanvas" style="display:none">您的浏览器不支持canvas</canvas>
    <div id="alert" class="alert">
        <div id="alert_text" class="alert_text">点击右上角,分享到朋友圈</div>
    </div>
    <form action="/upimages" method="post" enctype="multipart/form-data" style="display: none;">
        <input type="file" name="imgfile">
    </form>
    <script src="js/zepto.min.js"></script>
    <script src="js/touch-0.2.14.min.js"></script>
    <script src="js/exif.js"></script>
    <script src="js/hammer.min.js"></script>
    <script src="js/iscroll-zoom-min.js"></script>
    <script src="js/lrz.all.bundle.js"></script>
    <script src="js/PhotoClip.js"></script>
    <script src="js/swiper-bundle.min.js"></script>
    <script src="js/index.js"></script>
</body>

</html>

生成的图片保存在服务器上,用时间戳作为文件名,并返回图片url,供用户下载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值