画像にお絵描き(HTMLでCanvasを使用)

参照:https://www.cresco.co.jp/blog/entry/17344.html
参照:https://www.otwo.jp/blog/canvas-drawing/
実験対象バージョン:HTML5、CSS3

サンプル

取り込み
モード
描き込みタイプ
太さ
10
透過度
1
x:0 y:0 拡大率:1

HTML部

必要挿入
                            
                            
                            
                            
                        
bootstrapで画面のサイズが設定されているので、必要ですが、バージョンは適宜変更して下さい。
4行目は色の枠で必要です。
html
                            
取り込み
モード
描き込みタイプ
太さ
10
透過度
1
x:0 y:0 拡大率:1
位置が無いと滑らかに動かないので今のところ必須ですね。
javascript
                            // モード(描く/消しゴム/画像移動)
                            var mode = "1";
                            // 描き込みタイプ(ペン/直線/短径/円)
                            var inputType = "1";
                    
                            // 色・透過度
                            var canvasRgba = "rgba(0, 0, 0, 1)";
                            // 太さ
                            var brushSize = 10;
                            // 透過度
                            var alpha = 1;
                    
                            // クリックホールドフラグ
                            var holdClick = false;
                    
                            // 開始座標(X)
                            var startX = 0;
                            // 開始座標(Y)
                            var startY = 0;
                    
                            // 拡大率
                            var zoomRario = 1;
                    
                            // 各種canvasオブジェクト
                            var imageCvs = document.getElementById("imageCanvas");
                            var imageCtx = imageCvs.getContext("2d");
                    
                            var drawCvs = document.getElementById("drawCanvas");
                            var drawCtx = drawCvs.getContext("2d");
                    
                            var drawTempCvs = document.getElementById("drawTempCanvas");
                            var drawTempCtx = drawTempCvs.getContext("2d");
                    
                            var pointerCvs = document.getElementById("pointerCanvas");
                            var pointerCtx = pointerCvs.getContext("2d");
                    
                            window.addEventListener("load", function (e) {
                    
                                // マウスクリックイベント
                                pointerCvs.addEventListener("mousedown", mouseDown);
                                // マウス移動イベント
                                pointerCvs.addEventListener("mousemove", mouseMove);
                                // マウスクリック外しイベント
                                pointerCvs.addEventListener("mouseup", mouseUp);
                                // マウスホイールイベント
                                pointerCvs.addEventListener("wheel", mouseWheel);
                                // エリアから外れたときのイベント
                                pointerCvs.addEventListener("mouseout", function (e) {
                                    // ポインター除去
                                    pointerCtx.clearRect(0, 0, imageCvs.width, imageCvs.height)
                                    // マウスクリック外しイベントを呼び出し
                                    if (holdClick) {
                                        mouseUp(e);
                                    }
                                });
                    
                            });
                    
                            // モード変更時
                            $(function () {
                                $('[name="mode"]').on('change', function (e) {
                                    mode = $('input[name="mode"]:checked').val();
                    
                                    if (mode == "1") {
                                        // 描く
                                        $("#input-type-area").show();
                                        $("#size-area").show();
                                        $("#transparent-area").show();
                                        $("#range-area").show();
                                        $("#color-picker-area").show();
                                    } else if (mode == "2") {
                                        // 消しゴム
                                        $("#input-type-area").hide();
                                        $("#size-area").show();
                                        $("#transparent-area").hide();
                                        $("#range-area").show();
                                        $("#color-picker-area").hide();
                                    } else {
                                        // 画像移動
                                        $("#input-type-area").hide();
                                        $("#size-area").hide();
                                        $("#transparent-area").hide();
                                        $("#range-area").hide();
                                        $("#color-picker-area").hide();
                                    }
                                });
                            });
                    
                            // 描き込みタイプ変更時
                            $(function () {
                                $('[name="input-type"]').on('change', function (e) {
                                    inputType = $('input[name="input-type"]:checked').val();
                                });
                            });
                    
                            // 色変更時
                            $(function () {
                                $('#colorPicker').on('change', function (e) {
                    
                                    // colorPicker値設定
                                    $(this).val(e.detail[0]);
                    
                                    // canvas用にcanvasRgba形式へ変換
                                    canvasRgba = "rgba(" +
                                        parseInt(e.detail[0].substring(1, 3), 16) + ", " +
                                        parseInt(e.detail[0].substring(3, 5), 16) + ", " +
                                        parseInt(e.detail[0].substring(5, 7), 16) + ", " +
                                        alpha + ")";
                                });
                            });
                    
                            // 「ファイルを選択」ボタン
                            $(function () {
                                $('#uploadFile').on('change', function (e) {
                    
                                    var file = e.target.files[0];
                    
                                    if (file.type.indexOf("image") < 0) {
                                        alert("画像ファイルを指定してください。");
                                        return false;
                                    }
                    
                                    var reader = new FileReader();
                                    reader.onload = (function (file) {
                                        return function (e) {
                                            image(e.target.result);
                                            $("#explanation").hide();
                                            zoomRario = 1;
                                            zoom();
                                        };
                                    })(file);
                                    reader.readAsDataURL(file);
                                });
                            });
                    
                            // 描画クリア
                            $(function () {
                                $('#clear').on('click', function (e) {
                                    drawCtx.clearRect(0, 0, imageCvs.width, imageCvs.height);
                                });
                            });
                    
                            // canvasを画像で保存
                            $(function () {
                                $('#download').on('click', function (e) {
                                    // 結合後のcanvas要素を作成
                                    const combinedCanvas = document.createElement("canvas");
                                    combinedCanvas.width = imageCvs.width;
                                    combinedCanvas.height = imageCvs.height;
                    
                                    // 結合後のcanvasに4つのcanvasを描写
                                    const ctx = combinedCanvas.getContext("2d");
                                    ctx.drawImage(imageCvs, 0, 0);
                                    ctx.drawImage(drawCvs, 0, 0);
                                    ctx.drawImage(drawTempCvs, 0, 0);
                                    ctx.drawImage(pointerCvs, 0, 0);
                    
                                    // 結合後のcanvasをPNG形式でDataURLに変換
                                    const dataURL = combinedCanvas.toDataURL("image/png");
                    
                                    // ダウンロードリンクのhref属性にDataURLを設定
                                    $('#download').attr('href', dataURL);
                    
                                });
                            });

                            // 太さ変更時
                            function sizeChange(num) {
                                document.getElementById("size").innerHTML = num;
                                brushSize = num;
                            }

                            // 透過度変更時
                            function alphaChange(num) {
                                document.getElementById("transparent").innerHTML = num;
                                alpha = num;
                            
                                var temp = canvasRgba.replace("rgba(", "").replace(")", "").split(",");
                                canvasRgba = "rgba(" +
                                    temp[0] + ", " +
                                    temp[1] + ", " +
                                    temp[2] + ", " +
                                    num + ")"
                            }

                            // マウスクリックイベント
                            function mouseDown(e) {
                                holdClick = true;
                                // クリック開始座標を保持
                                startX = e.offsetX;
                                startY = e.offsetY;
                            }

                            // マウス移動イベント
                            function mouseMove(e) {
                            
                                // 座標表示
                                document.getElementById("dispX").innerHTML = e.offsetX;
                                document.getElementById("dispY").innerHTML = e.offsetY;
                            
                                if (mode == "1") { // モード:描く
                                
                                    if (inputType == "1" || inputType == "2") { // 描き込みタイプ:ペン or 直線
                                        pointer(e);
                                    }
                                
                                    if (holdClick) {
                                        if (inputType == "1") { // 描き込みタイプ:ペン
                                            drawPen(e);
                                        } else if (inputType == "2") { // 描き込みタイプ:直線
                                            drawLine(e);
                                        } else if (inputType == "3") { // 描き込みタイプ:短径
                                            drawRect(e);
                                        } else if (inputType == "4") { // 描き込みタイプ:円
                                            drawArc(e);
                                        }
                                    }
                                
                                } else if (mode == "2") { // モード:消しゴム
                                
                                    pointer(e);
                                
                                    if (holdClick) {
                                        drawErase(e);
                                    }
                                
                                } else { // モード:画像移動
                                
                                    if (holdClick) {
                                        imageMove(e);
                                    }
                                }
                            }

                            // マウスクリック外しイベント
                            function mouseUp(e) {
                            
                                holdClick = false;
                            
                                if (mode == "1") { // モード:描く
                                    if (inputType == "1") { // 描き込みタイプ:ペン
                                        drawPen(e);
                                    } else if (inputType == "2") { // 描き込みタイプ:直線
                                        drawLine(e);
                                    } else if (inputType == "3") { // 描き込みタイプ:短径
                                        drawRect(e);
                                    } else if (inputType == "4") { // 描き込みタイプ:円
                                        drawArc(e);
                                    }
                                } else if (mode == "2") { // モード:消しゴム
                                    drawErase(e);
                                }
                            }

                            // マウスホイール変更イベント
                            function mouseWheel(e) {
                            
                                // 拡大率算出
                                var temp = e.deltaY < 0 ? 1 : -1;
                                zoomRario += (0.1 * temp);
                            
                                // 拡大率は1~5まで
                                if (zoomRario < 1) {
                                    zoomRario = 1
                                } else if (zoomRario > 5) {
                                    zoomRario = 5
                                }
                            
                                // 小数点第二以下切り捨て
                                zoomRario = Math.round(zoomRario * 10) / 10;
                            
                                // 算出した拡大率で描画
                                zoom();
                                document.getElementById("dispScale").innerHTML = zoomRario;
                            
                                // ポインタも再設定
                                if ((mode == "1" && (inputType == "1" || inputType == "2"))
                                    || mode == "2") {
                                    pointer(e);
                                }
                            }

                            // drawCanvasエリア描画(ペン)
                            function drawPen(e) {
                            
                                drawCtx.lineWidth = brushSize;
                                drawCtx.strokeStyle = canvasRgba;
                                drawCtx.lineJoin = "round";
                                drawCtx.lineCap = "round";
                                drawCtx.globalCompositeOperation = 'source-over';
                                drawCtx.beginPath();
                                drawCtx.moveTo(startX, startY); // 開始座標(前回座標)
                                drawCtx.lineTo(e.offsetX, e.offsetY); // 終了座標(現在座標)
                                drawCtx.stroke(); // 直線を描画
                                drawCtx.closePath();
                            
                                // 次の描画に向けて現在の座標を保持(開始・終了を同じ座標で描画すると、マウスを高速に移動したときに歯抜け状態になる)
                                startX = e.offsetX;
                                startY = e.offsetY;
                            }

                            // drawCanvasエリア描画(直線)
                            function drawLine(e) {
                            
                                // 一時的描画Canvasクリア
                                drawTempCtx.clearRect(0, 0, imageCvs.width, imageCvs.height)
                            
                                if (holdClick) {
                                    // クリックホールド中は一時的描画Canvasに対して描画
                                    targateCtx = drawTempCtx;
                                } else {
                                    targateCtx = drawCtx;
                                }
                            
                                targateCtx.lineWidth = brushSize;
                                targateCtx.strokeStyle = canvasRgba;
                                targateCtx.lineCap = "round"; // 先端の形状
                                targateCtx.globalCompositeOperation = 'source-over';
                                targateCtx.beginPath();
                                targateCtx.moveTo(startX, startY); // 開始座標(クリック開始座標)
                                targateCtx.lineTo(e.offsetX, e.offsetY); // 終了座標(現在座標)
                                targateCtx.stroke(); // 直線を描画
                                targateCtx.closePath();
                            }

                            // drawCanvasエリア描画(短径)
                            function drawRect(e) {
                            
                                // 一時的描画Canvasクリア
                                drawTempCtx.clearRect(0, 0, imageCvs.width, imageCvs.height)
                            
                                if (holdClick) {
                                    // クリックホールド中は一時的描画Canvasに対して描画
                                    targateCtx = drawTempCtx;
                                } else {
                                    targateCtx = drawCtx;
                                }
                            
                                targateCtx.fillStyle = canvasRgba;
                                targateCtx.globalCompositeOperation = 'source-over';
                            
                                targateCtx.beginPath();
                                // クリック開始座標~現在座標で短径を描画
                                targateCtx.fillRect(startX, startY, e.offsetX - startX, e.offsetY - startY);
                                targateCtx.closePath();
                            }

                            // drawCanvasエリア描画(円)
                            function drawArc(e) {
                            
                                // 一時的描画Canvasクリア
                                drawTempCtx.clearRect(0, 0, imageCvs.width, imageCvs.height)
                            
                                if (holdClick) {
                                    // クリックホールド中は一時的描画Canvasに対して描画
                                    targateCtx = drawTempCtx;
                                } else {
                                    targateCtx = drawCtx;
                                }
                            
                                targateCtx.fillStyle = canvasRgba;
                                targateCtx.globalCompositeOperation = 'source-over';
                            
                                var centerX = Math.max(startX, e.offsetX) - Math.abs(startX - e.offsetX) / 2;
                                var centerY = Math.max(startY, e.offsetY) - Math.abs(startY - e.offsetY) / 2;
                                var distance = Math.sqrt(Math.pow(startX - e.offsetX, 2) + Math.pow(startY - e.offsetY, 2));
                            
                                targateCtx.beginPath();
                                // クリック開始座標~現在座標の中間を中心点として円を描画
                                targateCtx.arc(centerX, centerY, distance / 2, 0, Math.PI * 2, true);
                                targateCtx.fill();
                                targateCtx.closePath();
                            }

                            // drawCanvasエリア描画(消しゴム)
                            function drawErase(e) {
                            
                                drawCtx.lineWidth = brushSize;
                                drawCtx.lineCap = "round"; // 先端の形状
                                drawCtx.strokeStyle = "rgba(255, 255, 255, 1)"; // 色はなんでもよいが、透過度は1にする
                                drawCtx.globalCompositeOperation = 'destination-out' // 塗りつぶした個所を透明化
                                drawCtx.beginPath();
                                drawCtx.moveTo(startX, startY); // 開始座標(前回座標)
                                drawCtx.lineTo(e.offsetX, e.offsetY); // 終了座標(現在座標)
                                drawCtx.stroke(); // 描画
                                drawCtx.closePath();
                            
                                // 次の描画に向けて現在の座標を保持(開始座標・終了座標を同じ座標にしてしまうと、マウスを高速に移動したときに歯抜け状態になる)
                                startX = e.offsetX;
                                startY = e.offsetY;
                            }

                            // pointerCanvasエリア描画
                            function pointer(e) {
                            
                                // 事前のポインタ描画を除去
                                pointerCtx.clearRect(0, 0, imageCvs.width, imageCvs.height)
                            
                                if (mode == "2") {
                                    // モード:消しゴムのときは白固定
                                    pointerCtx.strokeStyle = "rgba(255, 255, 255, 1)";
                                } else {
                                    pointerCtx.strokeStyle = canvasRgba; // 事前に設定していた色
                                }
                            
                                pointerCtx.lineWidth = brushSize; // 太さ
                                pointerCtx.lineCap = "round"; // 円
                            
                                pointerCtx.beginPath();
                                pointerCtx.moveTo(e.offsetX, e.offsetY);
                                pointerCtx.lineTo(e.offsetX, e.offsetY); // 開始座標と終了座標を同じ
                                pointerCtx.stroke(); // 描画
                                pointerCtx.closePath();
                            }

                            // imageCanvasエリア画像設定
                            function image(src) {
                            
                                var img = new Image();
                                img.src = src;
                                img.onload = () => {
                                    // canvasエリアと画像のスケールを計算(縦・横 スケール値が低い方を採用)
                                    var scale =
                                        Math.min(
                                            $('#canvas-area').width() / img.naturalWidth,
                                            $('#canvas-area').height() / img.naturalHeight);
                                
                                    // canvasエリアの高さ・幅を設定
                                    imageCvs.width = img.width * scale;
                                    imageCvs.height = img.height * scale;
                                
                                    drawCvs.width = imageCvs.width;
                                    drawCvs.height = imageCvs.height;
                                
                                    drawTempCvs.width = imageCvs.width;
                                    drawTempCvs.height = imageCvs.height;
                                
                                    pointerCvs.width = imageCvs.width;
                                    pointerCvs.height = imageCvs.height;
                                
                                    // 画像を縮小して設定
                                    imageCtx.drawImage(img, 0, 0, imageCvs.width, imageCvs.height);
                                };
                            }

                            // 拡大縮小処理
                            function zoom() {
                            
                                // 拡大縮小の起点を設定
                                $("#imageCanvas").css({
                                    "transform-origin":
                                        document.getElementById("dispX").innerHTML + "px " +
                                        document.getElementById("dispY").innerHTML + "px"
                                });
                                $("#drawCanvas").css({
                                    "transform-origin":
                                        document.getElementById("dispX").innerHTML + "px " +
                                        document.getElementById("dispY").innerHTML + "px"
                                });
                                $("#drawTempCanvas").css({
                                    "transform-origin":
                                        document.getElementById("dispX").innerHTML + "px " +
                                        document.getElementById("dispY").innerHTML + "px"
                                });
                                $("#pointerCanvas").css({
                                    "transform-origin":
                                        document.getElementById("dispX").innerHTML + "px " +
                                        document.getElementById("dispY").innerHTML + "px"
                                });
                            
                                // 拡大縮小
                                $("#imageCanvas").css({ "transform": "scale(" + zoomRario + ")" });
                                $("#drawCanvas").css({ "transform": "scale(" + zoomRario + ")" });
                                $("#drawTempCanvas").css({ "transform": "scale(" + zoomRario + ")" });
                                $("#pointerCanvas").css({ "transform": "scale(" + zoomRario + ")" });
                            }

                            // 画像移動処理
                            function imageMove(e) {
                            
                                // 対象領域の長さ
                                var targetWidth = $("#imageCanvas").width();
                                var targetHeight = $("#imageCanvas").height();
                            
                                // 起点位置の取得
                                var origin = $("#imageCanvas").css('transform-origin');
                                var origins = origin.replaceAll("px", "").split(" ");
                            
                                // 起点位置に移動量を加算
                                var moveX = Number(origins[0]) + (startX - e.offsetX);
                                var moveY = Number(origins[1]) + (startY - e.offsetY);
                            
                                // 起点位置を対象範囲内に設定
                                if (moveX < 0) {
                                    moveX = 0;
                                } else if (moveX > targetWidth) {
                                    moveX = targetWidth;
                                }
                                if (moveY < 0) {
                                    moveY = 0;
                                } else if (moveY > targetHeight) {
                                    moveY = targetHeight;
                                }
                            
                                // 起点位置を変更
                                $("#imageCanvas").css({ "transform-origin": moveX + "px " + moveY + "px" });
                                $("#drawCanvas").css({ "transform-origin": moveX + "px " + moveY + "px" });
                                $("#drawTempCanvas").css({ "transform-origin": moveX + "px " + moveY + "px" });
                                $("#pointerCanvas").css({ "transform-origin": moveX + "px " + moveY + "px" });
                            }
                            
                        
168行目以降は別のjsファイルにするのもありだと思います。
画像移動は、サンプルからそのまま持ってきているだけです。この移動が何をしているのか理解できていないためです(笑)