一、效果预览

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

二、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="data: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="data:images/frame_1.png"></div><div class="swiper-slide"><img src="data:images/frame_2.png"></div><div class="swiper-slide"><img src="data:images/frame_3.png"></div></div></div><img src="data: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="data: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,供用户下载。

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

  1. 前端小白分享|canvas用户上传图片选择相框并长按保存到本地

    结合了几篇文章写(拼凑)出的用户上传图片然后选择相框并长按保存到本地的功能 兼容苹果手机!!!  分享给像我一样的前端小白~ 先上图: 上代码: upload.html <!DOCTYPE ht ...

  2. node.js+室内装修风格选择系统 毕业设计-附源码211552

    node.js室内装修风格选择系统 摘  要 随着互联网技术的深入发展和成熟,"物联网时代"到来的呼声日益高涨,国家"十二五"规划中也明确将物联网作为战略性新兴 ...

  3. node.js 生成文件_如何使用Node.js在几秒钟内生成模拟数据

    node.js 生成文件 介绍 (Introduction) In most of the applications, you need to have some static JSON data w ...

  4. node.js使用puppeteer来html生成pdf

    文章目录 一,环境: 二,目录结构 1, index.js 2,package.json 3,package-lock.json 4,.gitignore 三,windows10使用 可以重复生成 四 ...

  5. 用 node.js 爬取煎蛋网照片(仅做学习之用)

    主要是用到 puppeteer npm i puppeteer 具体配置看官网 入口文件 const puppeteer = require('puppeteer'); const { mn } = ...

  6. Node.js 和 Python之间如何进行选择?教你一招搞定

    最近,Node.js 和 Python 都因其广泛的特性和功能在市场上大受欢迎.在你开发Web应用程序的后端时,这两种技术都是你的首选. 众所周知,每个项目都有其自己的规范和需求,因此,选择一个合适的 ...

  7. Node.js接入支付宝(蚂蚁金服)支付

    最近项目(Android和Ios)中需要接入付费功能(支付宝和微信),下面就先来介绍下接入支付宝的流程.文章主要分为三大块: 第一块是如何在蚂蚁金服的开放平台创建一个应用并且配置开发选项. 第二块是n ...

  8. 优秀的 Node.js 包汇总

    前端资源分享小程序<前端Tool>欢迎浏览. Web框架 Express - Web应用程序框架,为构建单页和多页以及混合Web应用程序提供了一组强大的功能. Next.js - Reac ...

  9. 微信公众号开发总结(Node.js + express + winston)

    关于订阅号.服务号.企业号 官方定位 订阅号:主要偏于为用户传达资讯(类似报纸杂志),认证后每天可以群发一条消息,可达到宣传效果,构建与读者之间更好的沟通和管理模式. 服务号:主要偏于服务交互(类似银 ...

最新文章

  1. 如何开展软件架构之需求分析3
  2. 聊聊redisson的DelayedQueue
  3. 【源资讯 第36期】赶超 Java 和 PHP,Python 成最热门编程语言?
  4. 教你怎么在vi和vim上查找字符串
  5. mysql知识测试_MySQL基础知识测试
  6. 用Java分割大型XML文件
  7. oracle10g中获得可更新的(修改、增加等) ResultSet
  8. java程序设计与问题求解第八版pdf_《Java程序设计与问题求解(第7版)》怎么样_目录_pdf在线阅读 - 课课家教育...
  9. linux查看cpu、内存、版本信息
  10. 英特尔 超核芯显卡 620mac_2020双11装机。科学计算工作站配置推荐。i9-10980XE加3080显卡加64G ECC内存...
  11. php斐波那契数列循环,两种php实现斐波那契数列的方法
  12. 系统集成项目管理工程师的考试,下午题怎么突破?
  13. MySQL-存储过程-高效清理数据
  14. 算法题目打卡:Ques20201007
  15. App推广渠道如何统计的解决方案
  16. palantir_Palantir开源的两个库– Cinch和Sysmon
  17. webAPP如何实现移动端拍照上传(Vue组件示例)?
  18. 中间继电器DZY-204/DC110V
  19. 如何在Excel中将文件大小中有GB、MB的信息转化成统一格式
  20. python画柱形图显示数值_python画柱状图--不同颜色并显示数值的方法

热门文章

  1. python处理json数据——网易云评论爬取
  2. pandas进行one-hot编码
  3. 全套阿里巴巴开发手册和大神解读一起给你【2022年整理】
  4. 全媒体运营师胡耀文教你:4个私域流量直播案例复盘
  5. 火狐浏览器拒绝代理服务器连接
  6. MacBook电脑添加环境变量
  7. SpringBoot 使用ApplicationContext 及 getbean的两种方式
  8. Error type 三: Activity Class {.} dose not exist【解决方案】
  9. LeetCode中等题之TinyURL 的加密与解密
  10. 什么是序列化?如何实现序列化