前端之canvas實現電子簽約完成線上簽署功能
最近發現現在租房還是簽合同,越來越多采用電子簽約的方式進行,好處不用多說節約成本,節約時間。抱著好奇的心理,嘗試自己動手實現一個電子簽。原來并不復雜主要通過了canvas繪畫能力進行實現的。
主要功能
- 創建合同模板:使用HTML和CSS創建一個合同模板。
- 添加簽名區域:在合同模板中添加一個區域用于簽名。
- 實現簽名功能:使用
- 將簽名保存到合同模板:將簽名繪制到合同模板中。
- 提供下載功能:允許用戶下載帶有簽名的合同png/pdf格式。
展示效果如下


實現
主要三個重要點:實現簽名,簽名放到指定位置,保存pdf/png
1. 實現簽名
const canvas = $('#signatureCanvas')[0]// document.getElementById('signatureCanvas');
const ctx = canvas.getContext('2d');
let drawing = false;
$('#signatureCanvas').on('mousedown touchstart', (e) => {
drawing = true;
draw(e);
});
$('#signatureCanvas').on('mousemove touchmove', (e) => {
if (drawing) {
draw(e);
}
});
$('#signatureCanvas').on('mouseup mouseout touchend', () => {
drawing = false;
ctx.beginPath();
});
function draw(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.strokeStyle = '#000000';
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
2. 簽名放到指定位置
//取消簽名
document.getElementById('clearButton').addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
// 保存簽名
document.getElementById('saveButton').addEventListener('click', () => {
const dataURL = canvas.toDataURL();
let name = '.' + signName
if (name) {
const signTexts = document.querySelectorAll(name);
signTexts.forEach(signText => {
signText.style.backgroundImage = `url(${dataURL})`;
signText.style.backgroundSize = 'cover';
signText.textContent = ''; // 清空文本內容
});
$('.pop').hide();
}
});
3. 保存pdf/png
這里用到了html2canvas保存圖片格式,html2pdf保存pdf格式
// 下載簽名與輸入完成后的后合同
$('#downloadAgree').click(function () {
// 你可以指定要轉換為PDF的元素
const element = document.body;
/*
html2pdf()
.from(element)
.save('page.pdf');
*/
// 將當前頁面保存為png格式
html2canvas(element).then(canvas => {
// 創建一個a標簽來下載PNG文件
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = 'page.png';
element.appendChild(link);
link.click();
element.removeChild(link);
});
});
完成代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="author" content="cypking"></meta>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
</head>
<style>
html,
body {
height: 100%;
width: 100%;
padding: 20px;
}
.pop {
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, .5);
display: flex;
align-items: center;
justify-content: center;
}
.mark {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
text-align: center;
margin: auto;
}
.sign {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
input {
display: inline-block;
margin-left: 10px;
margin-top: 10px;
height: 24px;
line-height: 24px;
}
dl {
display: flex;
flex-direction: column;
align-items: flex-end;
}
dt,
dd {
display: flex;
align-items: center;
}
.sign-text,
.sign-date,
.sign-text1,
.sign-date1 {
margin-top: 10px;
height: 40px;
width: 80px;
border: 1px dashed #ccc;
display: flex;
align-items: center;
text-align: center;
justify-content: center;
border-radius: 4px;
cursor: pointer;
}
#signatureCanvas {
background-color: white;
}
#downloadAgree {
display: inline-block;
margin: 20px auto 40px;
}
.controlPanel {
position: fixed;
bottom: 20px;
right: 20px;
}
.party {
width: 50px;
height: 50px;
border-radius: 100%;
border: 1px dashed black;
position: relative;
margin-bottom: 20px;
}
.partya::after {
content: '甲';
width: 16px;
height: 16px;
border-radius: 100%;
font-size: 12px;
line-height: 16px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
.partyb::after {
content: '乙';
width: 16px;
height: 16px;
border-radius: 100%;
font-size: 12px;
line-height: 16px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
}
</style>
<body>
<span>??</span>
<!-- 合同 -->
<div class="agreement">
<h1> 居住房屋租賃合同</h1>
<br />
<br>出租人 (甲方): <input type="text" placeholder="請輸入" />
<br>證件類型及編號:<input type="text" placeholder="請輸入" />
<br>
<br>承租人 (乙方):<input type="text" placeholder="請輸入" />
<br>證件類型及編號:<input type="text" placeholder="請輸入" />
<br>
<br>根據《中華人民共和國民法典》等有關法律、法規的規定,甲、乙雙方在平等、自愿、公平和誠實信用的基礎上,經協商一致,就乙方承租甲方可依法出租的房屋事宜,訂立本合同。
<br>
<br>租金及支付方式
<br>租賃起止日期: 2025-02-01 至 2026-02-01
<br>租賃期限:1年
<br>
<br>方式①
<br>租金付款方式:年付****12元人民幣
<br>押金:***元人民幣
<br>總金額:***元人民幣
<br>(大寫):***
<br>期數:1次
<br>
<br>租客身份信息
<br>姓名:<input type="text" placeholder="請輸入" />
<br>性別:<input type="text" placeholder="請輸入" />
<br>聯系電話:<input type="text" placeholder="請輸入" />
<br>證件類型:<input type="text" placeholder="請輸入" />
<br>證件號碼:<input type="text" placeholder="請輸入" />
<br>工作:<input type="text" placeholder="請輸入" />
<br>
<br>條款內容
<br>第一條出租房屋情況和租賃用途
<br>1-1.甲方將*** 房屋出租給乙方。該房屋建筑面積為 *** 平方米。
<br>1-2.乙方向甲方承諾,承租該房屋僅用作居住使用。
<br>
<br>第二條交付日期和租賃期限
<br>2-1.甲、乙雙方約定,該房屋租賃期起止時間 2025-02-01 至 2026-02-01
<br>2-2.雙方同意,甲方于 *** 前將該房屋交付給乙方,由乙方進行驗收。
<br>
<br>第三條 租金及支付方式
<br>3-1.乙方使用 年付,即在租賃期限內一次性付清支付房租。
<br>3-2.甲乙雙方約定,在租賃期間內,該房屋采用方式①進行支付,房屋押金為 ***元,月租金 *** 元,一次性繳納費用*** 元,。(注:
雙方約定的租金為甲方凈得租金)最后一期租期不足期的,以實際居住天數計算該期租金。在租賃期間內,未經雙方協商一致,任何一方不得擅自調整租金標準。
<br>3-3.該房屋租賃期滿后,甲乙雙方協商一致續租的,乙方須支付甲方一個月租金作為甲方服務費,則戶屋押金自動轉為該房屋續租押金,不足部分(若有)由乙方補足。租賃期滿后甲、乙雙方確認不續租或本合同解除后,房屋押金除用以抵扣應由乙方承擔但尚未交納的費用(包括但不限于租金、違約金等本合同約定由乙方承擔的費用)
外,剩余部分 (若有) 應無息退還乙方。
<br>
<br>第四條其他相關費用
<br>4-1.租賃期限內,該房屋所發生的:水費、電費、煤氣費、有線電視費、網絡寬帶費、物業管理費、室內設施維修費 (人為使用不當的應由乙方承擔的除外)、保潔費、暖氣費中,甲方承擔的費用為:物業管理費、暖氣費。
<br>除甲方承擔的費用外,其他費用均由乙方承擔;
<br>4-2.甲方應依法收取本條項下費用(費用標準以國家電網、當地燃氣集團、自來水公司等服務提供商制定的為準)。
<br>
<br>第五條提供材料
<br>(一)本人及同住人員,真實有效身份證件正反面照片或紙質版復印件一份
<br>(二)本人及同住人員聯系方式
<br>(三)寵物種類及數量
<br>
<br>第六條 房屋使用要求和維修責任
<br>5-1.甲方應確保該房屋交付時符合規定的安全條件。乙方入住 30 天內附屬設施、設備非乙方使用不當或不合理使用有損壞或故障時,甲方負責維修;乙方入住超過 30
天后,附屬設施、設備有損壞或故障時由乙方自行維修解決,甲方不負責維修。
<br>5-2.乙方應對該房屋的使用安全負責。租賃期間,乙方應合理使用并愛護該房屋及其附屬設施、設備因乙方使用不當或不合理使用,致使該房屋及其附屬設施、設備損壞或發生故障的,乙方應負責修復,乙方不維修的,甲方可代為維修,費用由乙方承擔。
<br>5-3.租賃期間,甲方應定期對該房屋進行檢查、養護,保證該房屋及其附屬設施、設備處于正常的可使用和安全的狀態。甲方應在檢查養護前 3 日通知乙方。檢查養護時,乙方應予以配合。甲方應減少對乙方使用該房屋的影響。
<br>5-4.租賃期間,乙方須全力配合甲方因該房屋注冊公司 (含:工商、稅務、資質審批部門)所需要的實地核查,每年實地核查次數不超過 3次。若乙方以各種理由拒不配合視乙方違約,賠償甲方壹個月房租做為違約金。
<br>
<br>第七條 轉租、承租權轉讓和交換
<br>6-1.租賃期間,乙方無轉租權。
<br>6-2.租賃期間,乙方不得將該房屋轉讓給他人承租或與他人承租的居住房屋進行交換
<br>
<br>第八條 續租
<br>7-1. 該房屋租賃期限屆滿、乙方需繼續承租的,乙方應于租賃期限屆滿前三十日內向甲方提出續租,經甲、乙雙方協商一致后,重新簽訂合同。乙方支付續租賬單 (包括但不限于支付租金或補足續租押金)
即視為乙方已完全了解續租租期、續租租金等重要信息并確認該續租合同,與甲方建立房屋租賃關系。乙方不續租的,甲方有權在租賃期限屆滿前三十日內帶人看房,乙方需給予配合。
<br>
<br>第九條 房屋返還
<br>8-1.除甲方同意乙方續租外,乙方應在本合同的租期屆滿后 1日內返還該房屋
<br>8-2.乙方未經甲方書面同意逾期返還該房屋的,每逾期一日,乙方應按該房屋日租金的 三倍向甲方支付房屋占用費。
<br>8-3.除本合同附件另有約定外,乙方返還該房屋時,該房屋及其裝修、附屬設施和設備應當符合正常使用后的狀態。經甲方書面驗收認可后,相互結清各自應當承擔的費用
<br>
<br>第十條 解除本合同的條件
<br>10-1.甲、乙雙方同意在租賃期內,有下列情形之一的,本合同解除,雙方互不擔責:
<br>(一) 該房屋占用范圍內的土地使用權依法提前收回的;
<br>(二) 該房屋因公共利益需要被依法征收的;
<br>(三) 該房屋因城市建設需要被依法列入房屋拆遷許可范圍的;
<br>(四) 該房屋因不可抗力原因毀損、滅失,致使乙方不能正常使用的:
<br>(五) 簽訂本合同時,甲方已告知乙方該房屋已設定抵押,租賃期間被處分的;
<br>(六) 甲乙雙方協商一致解除本合同的。
<br>10-2.甲、乙雙方同意,甲方有下列情形之一的,乙方可書面通知甲方解除本合同,并有權要求甲方賠償損失。
<br>(一) 甲方未按合同約定按時交付該房屋,經乙方書面催告后 7日內仍未交付的;
<br>(二) 甲方交付的該房屋不符合本合同約定或存在重大質量缺陷,致使乙方不能正常使用的;
<br>
<br>10-3.甲、乙雙方同意,乙方有下列情形之一的,甲方可書面通知乙方解除本合同,并有權要求乙方賠償損失。
<br>(一) 乙方擅自改變房屋居住用途的;
<br>(二) 因乙方原因造成房屋結構損壞的:
<br>(三) 乙方擅自轉租該房屋、轉讓該房屋承租權或與他人交換各自承租的房屋的;
<br>(四) 乙方擅自增加承租同住人,或人均承租建筑面積、使用面積低于規定標準的
<br>(五) 乙方利用承租的居住房屋從事違法違規活動的:
<br>(六) 乙方逾期不支付本合同項下約定由乙方承擔的費用的(包括但不限于房屋押金、租金、水電煤費用等);
<br>(七)乙方擅自將該房屋鑰匙交付或配給非居住人員的;
<br>(八)乙方隱瞞、漏報、謊報自身傳染性疾病或隱性疾病的;
<br>(九) 乙方在未承租的房間堆放私人物品的。
<br>(十) 欠繳各項費用達月租金額 15%的。
<br>
<br>第十一條違約責任
<br>11-2.甲方未在本合同中告知乙方,該房屋出租前已抵押,造成乙方損失的,甲方應負責賠償。
<br>11-3.乙方未征得甲方書面同意或者超出甲方書面同意的范圍擅自裝修房屋或者增設附屬設施的,甲方可以要求乙方恢復房屋原狀并賠償損失。
<br>11-4.租賃期間,非本合同約定的情況,甲方提前解除合同,甲方應向乙方支付壹個月房屋租金作為違約金。若違約金不足抵付乙方損失的,甲方還應負責賠償。
<br>11-5.租賃期間,非本合同約定的情況,乙方中途擅自退租的,乙方應向甲方支付壹個月房屋租金作為違約金。若違約金不足抵付甲方損失的,乙方還應負責賠償。
<br>11-6.租賃期間,甲方有本合同第 10-2條約定情形之一的,應向乙方支付貳個月房屋租金作為違約金,若違約金不足抵付乙方損失的,甲方還應負責賠償。
<br>11-7.租賃期間,乙方有本合同第 10-3 條約定情形之一的,應向甲方支付貳個月房屋租金作為違約金,若違約金不足抵付甲方損失的,乙方還應負責賠償。
<br>11-8租賃期間,乙方承諾租賃期內不使用煤氣罐,不給電動車電池充電,若乙方私自違反承諾,造成意外事故與甲方無關,所有責任由乙方自行承擔。合同期內甲方不定期實地檢查屋內安全隱患,一經發現乙方使用煤氣罐或給電動車電池充電行為,視乙方嚴重違約,甲方有權收回房屋且押金不退。
<br>
<br>第十二條 其他條款
<br>12.1
租賃期間內,承租人是房屋的實際管理人,承租人需時刻注意防火、防盜、防觸電,不做危及自身人身安全的活動,房屋內發生的一切安全事故都由承租人來承擔,與出租人無關,包括但不限于高空拋物,水電煤氣使用不當,在房屋內摔倒等,如果承租人利用此房進行不正當的經營或者違法活動,出租方有權無條件的立刻收回房屋,給出租方造成損失的,要按照實際損失進行賠償。
<br>12.2.本合同未盡事宜,經甲、乙雙方協商一致,可訂立補充條款。本合同補充條款及附件,為本合同不可分割的一部分。
<br>12.3.本合同為電子合同,經乙方在本系統點擊”確認簽署“或支付本合同項下賬單 (該房屋租金及/或押金賬單) 后生效。
<br>12.4.本合同生效后,甲、乙雙方對合同內容的變更或補充應另行訂立合同,作為本合同的附件。本合同附件與本合同具有同等法律效力。
<br>
<br>第十三條 解決爭議的方式
<br>11-1.本合同由中華人民共和國法律、法規管轄。
<br>11-2.雙方在履行本合同過程中若發生爭議的,甲、乙雙方可協商解決或者向人民調解委員會申請調解,也可選擇下列第二種方式解決:
<br>(一) 提交當地仲裁委員會仲裁,(二) 依法向人民法院提起訴訟。
<br>
<br>其它內容
<br>房間家具清單:
<br>床2。床墊2。衣柜1。茶幾1。沙發1。電視柜 1。椅子4。窗簾3。冰箱1。洗衣機1。鑰匙 1。電卡 1。戶門禁 1。油煙機 1。洗碗機 1。茶幾1。餐桌1。
<br>公共家具清單:
<br>
<br>
<br>
<br>
<br>
<div class="sign">
<dl>
<dt>甲方(出租人)簽字:<div class="sign-text">簽字</div>
</dt>
<dd>日期:<div class="sign-date">簽字</div>
</dd>
</dl>
<dl>
<dt>乙方(承租人)簽字:<div class="sign-text1">簽字</div>
</dt>
<dd>日期:<div class="sign-date1">簽字</div>
</dd>
</dl>
</div>
</div>
<div class="controlPanel">
<div class="party partya"></div>
<div class="party partyb"></div>
<button id="downloadAgree">下載合同</button>
</div>
<div class="pop">
<div class="mark">
<canvas id="signatureCanvas" width="700" height="400" style="border:1px solid #000000;"></canvas>
<div>
<button id="clearButton">清除簽名</button>
<button id="saveButton">保存簽名</button>
</div>
</div>
</div>
</body>
<script>
$('.pop').hide();
let signName = ''
$(' .sign-text,.sign-date , .sign-text1,.sign-date1').click(function (e) {
console.log(111, e.target.className);
signName = e.target.className
$('.pop').show();
});
const canvas = $('#signatureCanvas')[0]// document.getElementById('signatureCanvas');
const ctx = canvas.getContext('2d');
let drawing = false;
$('#signatureCanvas').on('mousedown touchstart', (e) => {
drawing = true;
draw(e);
});
$('#signatureCanvas').on('mousemove touchmove', (e) => {
if (drawing) {
draw(e);
}
});
$('#signatureCanvas').on('mouseup mouseout touchend', () => {
drawing = false;
ctx.beginPath();
});
function draw(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.strokeStyle = '#000000';
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
//取消簽名
document.getElementById('clearButton').addEventListener('click', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
// 保存簽名
document.getElementById('saveButton').addEventListener('click', () => {
const dataURL = canvas.toDataURL();
let name = '.' + signName
if (name) {
const signTexts = document.querySelectorAll(name);
signTexts.forEach(signText => {
signText.style.backgroundImage = `url(${dataURL})`;
signText.style.backgroundSize = 'cover';
signText.textContent = ''; // 清空文本內容
});
$('.pop').hide();
}
});
</script>
<script>
// 下載簽名與輸入完成后的后合同
$('#downloadAgree').click(function () {
// 你可以指定要轉換為PDF的元素
const element = document.body;
/*
html2pdf()
.from(element)
.save('page.pdf');
*/
// 將當前頁面保存為png格式
html2canvas(element).then(canvas => {
// 創建一個a標簽來下載PNG文件
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = 'page.png';
element.appendChild(link);
link.click();
element.removeChild(link);
});
});
</script>
</html>

浙公網安備 33010602011771號