PK creative Spring Festival, I am participating in the “Spring Festival creative submission contest”, please see: Spring Festival creative submission Contest

preface

It’s the Spring Festival, ringing out the old and ringing in the new. Every family sticks Spring Festival couplets, whether in cities or in rural areas. Handwritten village couplets are more interesting, the traditional paper and pen must not be carried around to write, in view of this development of a set of online paper and pen system, you can take out a mobile phone at any time and anywhere to write Spring Festival couplets, good writing can be downloaded to the local, or even to the printing agency directly printed out.

Results show

On display

Demand analysis

  1. The font is thick and thin, the block is thin, the slow is thick
  2. The writing must not have broken ink
  3. Change font thickness smoothly
  4. You can adjust the word count dynamically
  5. You can save the Spring Festival couplets as pictures

Code development

Canvas is used as the main canvas of Spring Festival couplets, and curves are drawn on canvas to write Spring Festival couplets by mouse or touch events. The thickness of curves is controlled by monitoring the sliding speed of mouse or touch to achieve the effect similar to writing Spring Festival couplets with brush.

Spring Festival couplets layout

Canvas serves as the main canvas of Spring Festival couplets, and three buttons are set to reset the canvas, modify the word count of Spring Festival couplets, and download the pictures written in Spring Festival couplets to the local.

<div id="div" style="position: Relative "> <canvas ID ="canvas"> Your browser does not support canvas</canvas> <div ID ="controller"> <div ID =" clear_bTN "class=" op_bTN" onclick </div> <div id=" back_bTN "</div> Class = "op_btn" onclick = "onReset ()" > reset < / div > < div class = "clearfix" > < / div > < / div > < / div >Copy the code

Initialize Spring Festival couplets

The default width of Spring Festival couplet canvas is 300, and each word is a square of 300 X 300. The default word count of Spring Festival couplet is 7, and the white space on the line is 50px. The height of Spring Festival couplet is: width X word count + 100, and the code is as follows.

var context; var canvas; Var isMouseDown = false; Var lasloc = {x: 0, y: 0}; var lasTimeStamp = 0; var laslinewidth = -1; var strokeColor = "black"; var num = 7; $(function(){ canvasWidth = 300; canvasHeight = canvasWidth * num + 100; canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); canvas.width = canvasWidth; canvas.height = canvasHeight; drawBackGround(); })Copy the code

Fill canvas with a red background and mark each font position with div with a black background 5px high and 10px wide. The code is as follows.

Function drawBackGround(){context.fillstyle = "red"; function drawBackGround(){context.fillstyle = "red"; The context. FillRect (0, 0, canvasWidth, canvasHeight); $(".tag").remove(); var y = 50; for (var i = 0; i <= num; Var divElement = document.createElement("div"); var divElement = document.createElement("div"); divElement.style.background = "black"; divElement.style.position = "absolute"; divElement.style.top = y + "px"; divElement.style.left = "0px"; divElement.style.width = "10px"; divElement.style.height = "5px"; divElement.setAttribute("class","tag") $("#div").append(divElement); y += canvasWidth; }}Copy the code

Font thickness Settings

Set the maximum line width of the font to 20, the minimum to prevent broken ink, and the maximum speed to 2 and the minimum speed to 0.1. Dynamically calculate the line width by speed. In order to smooth the line width of the font, only modify 1/3 of the line width each time, the code is as follows.

// The faster the stroke speed, the thinner the pen, and vice versa! var maxlinewidth = 20; var minlinewidth = 1; var maxlinespeed = 2; Var minlinespeed = 0.1; // function CalClientWidth(t, s) { var v = s / t; var ResultLineWidth; If (v <= minlinespeed) ResultLineWidth = maxLineWidth; if (v <= minlinespeed) ResultLineWidth = maxLineWidth; else if (v >= maxlinespeed) ResultLineWidth = minlinewidth; else ResultLineWidth = maxlinewidth - (v - minlinespeed) / (maxlinespeed - minlinespeed) * (maxlinewidth - minlinewidth); if (laslinewidth == -1) return ResultLineWidth; return laslinewidth * 2 / 3 + ResultLineWidth * 1 / 3; }Copy the code

Set up mouse and touch listener

It is worth noting that the canvas of Spring Festival couplets is relatively long. In order to ensure that the writing position matches the writing position, the rolling distance of the scroll bar needs to be subtracted when calculating the ordinate. The code is as follows.

Onmousedown = function (e) {// Block the default event response e.preventDefault(); beginStock({x: e.clientX, y: e.clientY}); }; Onmouseup = function (e) {e.preventDefault(); endStock(); }; Canvas. Onmouseout = function (e) {e.preventDefault(); endStock(); }; // Canvas. onmousemove = function (e) {e.preventDefault(); if (isMouseDown) { moveStock({x: e.clientX, y: e.clientY}); }}; // Canvas. AddEventListener (' touchStart ', function (e) {e.preventDefault(); // Let touch = e.touches[0]; let touch = e.touches[0]; beginStock({x: touch.pageX, y: touch.pageY}); }); canvas.addEventListener('touchmove', function (e) { e.preventDefault(); if (isMouseDown) { let touch = e.touches[0]; moveStock({x: touch.pageX, y: touch.pageY}); }}); canvas.addEventListener('touchend', function (e) { e.preventDefault(); endStock(); }); function beginStock(point) { isMouseDown = true; lasloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop)); lasTimeStamp = new Date().getTime(); } function endStock(point) { isMouseDown = false; } function moveStock(point) { var curloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop)); var curTimeStamp = new Date().getTime(); var s = calDistance(curloc, lasloc); var t = curTimeStamp - lasTimeStamp; var lineWidth = CalClientWidth(t, s); // Concretely draw, after mouse down context.beginPath(); context.moveTo(lasloc.x, lasloc.y); context.lineTo(curloc.x, curloc.y); context.strokeStyle = strokeColor; context.lineWidth = lineWidth; LineCap = "round"; context.lineCap = "round"; context.lineJoin = "round"; context.stroke(); lasloc = curloc; lasTimeStamp = curTimeStamp; laslinewidth = lineWidth; } function windowToCanvas(x, Y) {/ / contains the canvas on the distance of the canvas and the left margin var bbox = canvas. GetBoundingClientRect (); return {x: Math.round(x - bbox.left), y: Math.round(y-bbox.top)}} function calDistance(loc1, loc2) { return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y)); }Copy the code

Set function button

The logic is relatively simple, without explanation.

function downloadImage(){ var image = canvas.toDataURL("image/png"); var linkElement = document.createElement("a"); linkElement.setAttribute('href',image); LinkElement. SetAttribute (' downLoad ', 'Spring Festival couplets); linkElement.click(); drawBackGround(); } function changeNum(){var s = prompt(" please fill in the Spring Festival couplet number "); if(! S.m atch (\ d + /) | | new Number (s) < 1) {alert (" please fill in the correct Numbers ") return; } num = new Number(s); canvasHeight = canvasWidth * num + 100; canvas.height = canvasHeight; drawBackGround(); } function onReset() { drawBackGround(); }Copy the code

The complete code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
    <script type="text/javascript" src="${rc.contextPath}/static/js/jquery.min.js"></script>
</head>
<body>
<div id="div" style="position: relative">
    <canvas id="canvas">你的浏览器不支持canvas</canvas>
    <div id="controller">
        <div id="clear_btn" class="op_btn" onclick ="changeNum()"> 字数</div>
        <div id="save_btn" onclick="downloadImage()" class="op_btn">保存</div>
        <div id="back_btn" class="op_btn" onclick="onReset()">重置</div>
        <div class="clearfix"></div>
    </div>
</div>
<script>
    var context;
    var canvas;
    var canvasWidth
    var canvasHeight
    //判断是否按下鼠标
    var isMouseDown = false;
    //记录上一次鼠标所在位置
    var lasloc = {x: 0, y: 0};
    var lasTimeStamp = 0;
    var laslinewidth = -1;
    var strokeColor = "black";
    var num = 7;
    $(function(){
        canvasWidth = 300;
        canvasHeight = canvasWidth * num + 100;
        canvas = document.getElementById("canvas");
        context = canvas.getContext("2d");
        canvas.width = canvasWidth;
        canvas.height = canvasHeight;
        drawBackGround();
        //鼠标按下
        canvas.onmousedown = function (e) {
            //阻止默认事件响应
            e.preventDefault();
            beginStock({x: e.clientX, y: e.clientY});
        };
        //鼠标松开
        canvas.onmouseup = function (e) {
            e.preventDefault();
            endStock();
            //console.log("mouseup");
        };
        //鼠标移出指定对象时发生
        canvas.onmouseout = function (e) {
            e.preventDefault();
            endStock();
        };
        //鼠标移动过程中
        canvas.onmousemove = function (e) {
            e.preventDefault();
            if (isMouseDown) {
                moveStock({x: e.clientX, y: e.clientY});
            }
        };
        //移动端(触碰相关的事件)
        canvas.addEventListener('touchstart', function (e) {
            e.preventDefault();
            //触碰事件,也可能是多点触碰,就是第一个
            let touch = e.touches[0];
            beginStock({x: touch.pageX, y: touch.pageY});
        });
        canvas.addEventListener('touchmove', function (e) {
            e.preventDefault();
            if (isMouseDown) {
                //console.log("mousemove");
                let touch = e.touches[0];
                moveStock({x: touch.pageX, y: touch.pageY});

            }
        });
        canvas.addEventListener('touchend', function (e) {
            e.preventDefault();
            endStock();
        });
    })

    //屏幕坐标点转化为canvas画布坐标,定位canvas画布上的坐标
    function windowToCanvas(x, y) {
        //包含canvas距离画布的上和左边距
        var bbox = canvas.getBoundingClientRect();
        return {x: Math.round(x - bbox.left), y: Math.round(y - bbox.top)}
    }

    //通过两点计算出两点之间距离
    function calDistance(loc1, loc2) {
        return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2.y));
    }

    //笔画速度越快,笔越细,反之越粗!
    var maxlinewidth = 20;
    var minlinewidth = 1;
    var maxlinespeed = 2;
    var minlinespeed = 0.1;

    //计算毛笔宽度
    function CalClientWidth(t, s) {
        var v = s / t;
        var ResultLineWidth;
        //处理速度很慢和很快的情况
        if (v <= minlinespeed)
            ResultLineWidth = maxlinewidth;
        else if (v >= maxlinespeed)

            ResultLineWidth = minlinewidth;
        else
            ResultLineWidth = maxlinewidth - (v - minlinespeed) / (maxlinespeed - minlinespeed) * (maxlinewidth - minlinewidth);
        if (laslinewidth == -1)
            return ResultLineWidth;
        return laslinewidth * 2 / 3 + ResultLineWidth * 1 / 3;
    }

    //设置背景及字体位置标识
    function drawBackGround(){
        context.fillStyle = "red";
        context.fillRect(0,0,canvasWidth,canvasHeight);
        $(".tag").remove();
        var y = 50;
        for (var i = 0; i <= num; i++) {
            //具体的绘制,鼠标按下之后
            var divElement = document.createElement("div");
            divElement.style.background = "black";
            divElement.style.position = "absolute";
            divElement.style.top = y + "px";
            divElement.style.left = "0px";
            divElement.style.width = "10px";
            divElement.style.height = "5px";
            divElement.setAttribute("class","tag")
            $("#div").append(divElement);
            y += canvasWidth;
        }
    }

    function beginStock(point) {
        isMouseDown = true;
        lasloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));
        lasTimeStamp = new Date().getTime();
    }

    function endStock(point) {
        isMouseDown = false;
    }

    function moveStock(point) {
        var curloc = windowToCanvas(point.x, point.y - Math.max($("body").scrollTop(),document.documentElement.scrollTop));
        var curTimeStamp = new Date().getTime();

        var s = calDistance(curloc, lasloc);
        var t = curTimeStamp - lasTimeStamp;

        var lineWidth = CalClientWidth(t, s);

        //具体的绘制,鼠标按下之后
        context.beginPath();
        context.moveTo(lasloc.x, lasloc.y);
        context.lineTo(curloc.x, curloc.y);
        context.strokeStyle = strokeColor;
        context.lineWidth = lineWidth;
        //设置线条的帽子,是线条平滑
        context.lineCap = "round";
        context.lineJoin = "round";
        context.stroke();
        lasloc = curloc;
        lasTimeStamp = curTimeStamp;
        laslinewidth = lineWidth;
    }

    function downloadImage(){
        var image = canvas.toDataURL("image/png");
        var linkElement = document.createElement("a");
        linkElement.setAttribute('href',image);
        linkElement.setAttribute('downLoad','春联');
        linkElement.click();
        drawBackGround();
    }

    function changeNum(){
        var s = prompt("请填写春联字数");
        if(!s.match(/\d+/) || new Number(s) < 1){
            alert("请填写正确数字")
            return;
        }
        num = new Number(s);
        canvasHeight = canvasWidth * num + 100;
        canvas.height = canvasHeight;
        drawBackGround();
    }

    function onReset() {
        drawBackGround();
    }
</script>

<style>
    /* CSS Document */

    #canvas {
        display: block;
        margin: 0 auto;
        border: 1px solid #aaa;
    }

    #controller {
        position: fixed;
        width: 200px;
        bottom: 180px;
        right: -90px;
        transform: rotate(90deg);
    }

    .op_btn {
        float: right;
        margin: 10px 0 0 10px;
        border: 1px solid #efefef;
        width: 50px;
        height: 25px;
        line-height: 25px;
        font-size: 12px;
        text-align: center;
        border-radius: 2px;
        cursor: pointer;
        background: #fff;
    }

    .op_btn {
        background-color: red;
        color:#fff;
    }

    .clearfix {
        clear: both;
    }
</style>

</body>
</html>
Copy the code