基于Javascript實現(xiàn)網(wǎng)頁版的繪圖板
項目簡介
這是一個基于HTML5 Canvas和jQuery實現(xiàn)的簡單網(wǎng)頁版繪圖編輯器。提供了基礎的圖片編輯功能,包括畫筆工具、橡皮擦、亮度/對比度調(diào)節(jié)等功能。可以用于簡單的圖片編輯和繪圖需求。
主要功能
1. 基礎繪圖工具
畫筆工具:支持自定義顏色和大小
橡皮擦工具:支持自定義大小
撤銷/重做:支持多步操作歷史記錄
2. 圖片處理
圖片上傳:支持上傳本地圖片
圖片調(diào)整:支持亮度和對比度調(diào)節(jié)
圖片保存:可將編輯后的圖片保存到本地
技術實現(xiàn)要點
1. 畫布初始化
const canvas = $('#imageCanvas')[0]; const ctx = canvas.getContext('2d');
使用HTML5 Canvas作為繪圖基礎,默認設置白色背景。
2. 繪圖實現(xiàn)
繪圖功能通過監(jiān)聽鼠標事件實現(xiàn):
- mousedown:開始繪制
- mousemove:持續(xù)繪制
- mouseup/mouseleave:結(jié)束繪制
關鍵代碼:
function startDrawing(e) { isDrawing = true; ctx.beginPath(); if (currentTool === 'eraser') { ctx.save(); ctx.globalCompositeOperation = 'destination-out'; } else { ctx.globalCompositeOperation = 'source-over'; } ctx.moveTo(e.offsetX, e.offsetY); }
3. 橡皮擦實現(xiàn)
橡皮擦通過設置 Canvas 的 globalCompositeOperation 屬性為 ‘destination-out’ 來實現(xiàn)透明擦除效果:
if (currentTool === 'eraser') { ctx.save(); ctx.globalCompositeOperation = 'destination-out'; }
4. 圖片處理
圖片上傳后會自動調(diào)整大小以適應畫布,保持原始比例:
const scale = Math.min(canvas.width / img.width, canvas.height / img.height); const x = (canvas.width - img.width * scale) / 2; const y = (canvas.height - img.height * scale) / 2;
5. 亮度和對比度調(diào)節(jié)
使用 Canvas 的 filter 屬性實現(xiàn):
ctx.filter = `brightness(${brightness}%) contrast(${contrast}%)`;
6. 撤銷/重做功能
通過保存畫布狀態(tài)實現(xiàn):
function saveState() { undoStack.push(canvas.toDataURL()); redoStack = []; updateUndoRedoButtons(); }
使用說明
1.上傳圖片:點擊"上傳圖片"按鈕選擇本地圖片
2.基礎編輯:
- 使用畫筆工具進行繪制
- 使用橡皮擦工具擦除內(nèi)容
- 調(diào)節(jié)亮度和對比度滑塊修改圖片效果
3.撤銷/重做:使用對應按鈕進行操作歷史管理
4.保存圖片:點擊"保存"按鈕將結(jié)果保存為PNG格式
技術依賴
HTML5 Canvas
jQuery 3.7.1
現(xiàn)代瀏覽器(支持ES6+)
改進方向
1. 功能擴展
- 添加圖片旋轉(zhuǎn)/翻轉(zhuǎn)功能
- 實現(xiàn)圖片裁剪功能
- 添加更多濾鏡效果
- 實現(xiàn)圖層功能
2. 用戶體驗優(yōu)化
- 添加快捷鍵支持
- 優(yōu)化工具切換交互
- 添加操作提示
- 完善錯誤處理
3. 性能優(yōu)化
- 優(yōu)化大圖片處理
- 改進撤銷/重做機制
- 添加操作狀態(tài)緩存
- 優(yōu)化Canvas渲染性能
注意事項
圖片上傳大小沒有限制,但建議不要超過5MB
保存的圖片格式為PNG,支持透明度
瀏覽器需要支持Canvas的相關API
完整代碼
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>網(wǎng)頁版圖片編輯器</title> <!-- 引入 jQuery --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; } .container { max-width: 1200px; margin: 0 auto; } .toolbar { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; } .toolbar button { padding: 8px 15px; margin: 0 5px; border: 1px solid #ddd; border-radius: 4px; background: #fff; cursor: pointer; transition: all 0.3s ease; } .toolbar button:hover { background: #f5f5f5; } .toolbar button.active { background: #e0e0e0; } .canvas-container { position: relative; margin: 20px 0; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } #imageCanvas { border: 1px solid #ddd; margin: 0 auto; display: block; } .controls { background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-top: 20px; } .control-group { margin: 10px 0; } .control-group label { display: block; margin-bottom: 5px; } input[type="range"] { width: 200px; } input[type="color"] { width: 50px; height: 30px; padding: 0; border: none; } .hidden { display: none; } </style> </head> <body> <div class="container"> <div class="toolbar"> <input type="file" id="imageInput" class="hidden" accept="image/*"> <button id="uploadBtn">上傳圖片</button> <button id="undoBtn" disabled>撤銷</button> <button id="redoBtn" disabled>重做</button> <button id="saveBtn" disabled>保存</button> <button id="brushBtn">畫筆</button> <button id="eraserBtn">橡皮擦</button> </div> <div class="canvas-container"> <canvas id="imageCanvas" width="800" height="600"></canvas> </div> <div class="controls"> <div class="control-group"> <label for="brightnessRange">亮度:</label> <input type="range" id="brightnessRange" min="0" max="200" value="100"> <span id="brightnessValue">100%</span> </div> <div class="control-group"> <label for="contrastRange">對比度:</label> <input type="range" id="contrastRange" min="0" max="200" value="100"> <span id="contrastValue">100%</span> </div> <div class="control-group"> <label for="brushSize">畫筆大?。?lt;/label> <input type="range" id="brushSize" min="1" max="50" value="5"> <span id="brushSizeValue">5px</span> </div> <div class="control-group"> <label for="brushColor">畫筆顏色:</label> <input type="color" id="brushColor" value="#000000"> </div> </div> </div> <script> $(document).ready(function() { const canvas = $('#imageCanvas')[0]; const ctx = canvas.getContext('2d'); let isDrawing = false; let currentTool = 'brush'; let originalImage = null; let undoStack = []; let redoStack = []; // 初始化畫布,設置白色背景 ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); saveState(); // 工具選擇 $('#brushBtn').click(function() { currentTool = 'brush'; $(this).addClass('active'); $('#eraserBtn').removeClass('active'); }); $('#eraserBtn').click(function() { currentTool = 'eraser'; $(this).addClass('active'); $('#brushBtn').removeClass('active'); }); // 繪圖功能 function startDrawing(e) { isDrawing = true; ctx.beginPath(); if (currentTool === 'eraser') { ctx.save(); ctx.globalCompositeOperation = 'destination-out'; } else { ctx.globalCompositeOperation = 'source-over'; } ctx.moveTo(e.offsetX, e.offsetY); } function draw(e) { if (!isDrawing) return; if (currentTool === 'eraser') { ctx.save(); ctx.globalCompositeOperation = 'destination-out'; } else { ctx.globalCompositeOperation = 'source-over'; } ctx.lineTo(e.offsetX, e.offsetY); ctx.stroke(); if (currentTool === 'eraser') { ctx.restore(); } } function stopDrawing() { if (isDrawing) { isDrawing = false; ctx.closePath(); if (currentTool === 'eraser') { ctx.restore(); } saveState(); } } $('#imageCanvas').mousedown(startDrawing) .mousemove(draw) .mouseup(stopDrawing) .mouseleave(stopDrawing); // 畫筆控制 $('#brushColor').change(function() { ctx.strokeStyle = $(this).val(); }); $('#brushSize').on('input', function() { const size = $(this).val(); ctx.lineWidth = size; $('#brushSizeValue').text(size + 'px'); }); // 圖片上傳處理 $('#uploadBtn').click(function() { $('#imageInput').click(); }); $('#imageInput').change(function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(event) { const img = new Image(); img.onload = function() { // 保持圖片比例 const scale = Math.min(canvas.width / img.width, canvas.height / img.height); const x = (canvas.width - img.width * scale) / 2; const y = (canvas.height - img.height * scale) / 2; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, x, y, img.width * scale, img.height * scale); originalImage = img; saveState(); $('#saveBtn').prop('disabled', false); }; img.src = event.target.result; }; reader.readAsDataURL(file); } }); // 圖片調(diào)整 function applyAdjustments() { if (!originalImage) return; const brightness = $('#brightnessRange').val(); const contrast = $('#contrastRange').val(); // 計算保持寬高比的縮放 const scale = Math.min(canvas.width / originalImage.width, canvas.height / originalImage.height); const x = (canvas.width - originalImage.width * scale) / 2; const y = (canvas.height - originalImage.height * scale) / 2; ctx.filter = `brightness(${brightness}%) contrast(${contrast}%)`; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(originalImage, x, y, originalImage.width * scale, originalImage.height * scale); ctx.filter = 'none'; saveState(); } $('#brightnessRange, #contrastRange').on('input', function() { const value = $(this).val(); const id = $(this).attr('id'); $(`#${id}Value`).text(value + '%'); applyAdjustments(); }); // 撤銷/重做功能 function saveState() { undoStack.push(canvas.toDataURL()); redoStack = []; updateUndoRedoButtons(); } function updateUndoRedoButtons() { $('#undoBtn').prop('disabled', undoStack.length <= 1); $('#redoBtn').prop('disabled', redoStack.length === 0); } $('#undoBtn').click(function() { if (undoStack.length <= 1) return; redoStack.push(undoStack.pop()); const lastState = undoStack[undoStack.length - 1]; loadState(lastState); updateUndoRedoButtons(); }); $('#redoBtn').click(function() { if (redoStack.length === 0) return; const nextState = redoStack.pop(); undoStack.push(nextState); loadState(nextState); updateUndoRedoButtons(); }); function loadState(state) { const img = new Image(); img.onload = function() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); }; img.src = state; } // 保存功能 $('#saveBtn').click(function() { const link = document.createElement('a'); link.download = '編輯后的圖片.png'; link.href = canvas.toDataURL(); link.click(); }); // 設置初始畫筆屬性 ctx.strokeStyle = $('#brushColor').val(); ctx.lineWidth = $('#brushSize').val(); ctx.lineCap = 'round'; ctx.lineJoin = 'round'; }); </script> </body> </html>
到此這篇關于基于Javascript實現(xiàn)網(wǎng)頁版的繪圖板的文章就介紹到這了,更多相關Javascript繪圖板內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
layui checkbox默認選中,獲取選中值,清空所有選中項的例子
今天小編就為大家分享一篇layui checkbox默認選中,獲取選中值,清空所有選中項的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09微信小程序?qū)崿F(xiàn)獲取準確的騰訊定位地址功能示例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)獲取準確的騰訊定位地址功能,結(jié)合實例形式詳細分析了微信小程序使用騰訊地理位置接口的相關注冊、操作步驟及接口使用技巧,需要的朋友可以參考下2019-03-03IE6/7/8中Option元素未設value時Select將獲取空字符串
可以看到當忘記寫option的value時這些現(xiàn)代瀏覽器都會盡量返回正確的(客戶端程序員想要的)結(jié)果value,其容錯性比IE6/7/8做的更好。2011-04-04JavaScript中的property和attribute介紹
JavaScript中的property和attribute介紹,需要的朋友可以參考下。2011-12-12uniapp開發(fā)小程序的開發(fā)規(guī)范總結(jié)
uni-app 是一個使用 vue.js 開發(fā)跨平臺應用的前端框架,下面這篇文章主要給大家介紹了關于uniapp開發(fā)小程序的開發(fā)規(guī)范,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-07-07Bootstrap3 datetimepicker控件使用實例
這篇文章主要為大家詳細介紹了Bootstrap3 datetimepicker控件使用實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12JavaScript結(jié)合HTML DOM實現(xiàn)聯(lián)動菜單
這篇文章主要為大家詳細介紹了JavaScript結(jié)合HTML DOM實現(xiàn)聯(lián)動菜單,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04ASP中進行HTML數(shù)據(jù)及JS數(shù)據(jù)編碼函數(shù)
在有些時候我們無法控制亂碼的出現(xiàn), 比如發(fā)送郵件的時候有些郵件顯示亂碼, 比如Ajax返回數(shù)據(jù)總是亂碼. 怎么辦?2009-11-11