一個小小的記事本,除了基本的功能外,若是須要在用戶體驗方面作的更好,不少小細節須要進行考慮,如關閉前自動保存窗體信息,保存皮膚設置,以及快捷鍵功能等等。css
默認的主菜單在electron隱藏邊框後,依然能夠使用快捷鍵進行操做,在此基礎上直接寫一個具備點擊下拉效果的菜單便可。窗體的長寬、位置、最大化這幾個信息獲取後利用nodejs的fs模塊進行保存,保存爲json格式便於讀取和調用。換膚功能則採用替換css樣式文件。這就是三個主要功能的開發思路,其餘小細節在開發中逐步優化。html
主進程代碼node
// main.js const {app, BrowserWindow, ipcMain, Menu} = require('electron'); const path = require('path'); const fs = require('fs'); // 引入 NodeJS 的 fs 模塊 // 主菜單模板 const menuTemplate = [ { label: ' 文件 ', submenu: [ { label: '新建', accelerator: 'CmdOrCtrl+N', click: function() { mainWindow.webContents.send('action', 'new') } }, { label: '打開', accelerator: 'CmdOrCtrl+O', click: function() { mainWindow.webContents.send('action', 'open') } }, { label: '保存', accelerator: 'CmdOrCtrl+S', click: function() { mainWindow.webContents.send('action', 'save') } }, { label: '另存爲... ', accelerator: 'CmdOrCtrl+Shift+S', click: function() { mainWindow.webContents.send('action', 'save-as') } }, { type: 'separator' }, { label: '退出', click: function() { mainWindow.webContents.send('action', 'exit') } } ] }, { label: ' 編輯 ', submenu: [ { label: '返回', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重作', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, //分隔線 { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '複製', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘貼', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '刪除', accelerator: 'CmdOrCtrl+D', role: 'delete' }, { type: 'separator' }, //分隔線 { label: '全選', accelerator: 'CmdOrCtrl+A', role: 'selectall' }, { label: 'DevTools', accelerator: 'CmdOrCtrl+I', click: function() { mainWindow.webContents.openDevTools(); } }, { accelerator: 'CmdOrCtrl+R', role: 'reload' } ] } ]; // 主窗體 let mainWindow; // 安全退出初始化 let safeExit = false; // 構建主菜單 let menu = Menu.buildFromTemplate (menuTemplate); Menu.setApplicationMenu (menu); // 讀取窗體保存數據 var data = fs.readFileSync('./data.json'); var myData = JSON.parse(data); // 主窗體初始化 function createWindow() { mainWindow = new BrowserWindow({ x: myData.positionX, y: myData.positionY, width: myData.width, height: myData.height, minWidth: 400, minHeight: 300, frame: false, backgroundColor: '#000000', show: false, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } }); mainWindow.once('ready-to-show', () => { mainWindow.show(); }); // 加載頁面內容 mainWindow.loadFile('index.html'); // 開發者工具 //mainWindow.webContents.openDevTools(); // 窗體生命週期 close 操做 mainWindow.on('close', (e) => { if(!safeExit) { e.preventDefault(); } mainWindow.webContents.send('action', 'exit'); }); // 窗體生命週期 closed 操做 mainWindow.on('closed', function() { mainWindow = null; }); } // 程序生命週期 ready app.on('ready', createWindow); // 程序生命週期 window-all-closed app.on('window-all-closed', function() { if (process.platform !== 'darwin') app.quit(); }); // 程序生命週期 activate app.on('activate', function() { if (mainWindow === null) createWindow(); }); // 窗體操做 ipcMain.on('reqaction', (event, arg) => { switch(arg) { case 'exit': // 接收退出命令 safeExit = true; app.quit(); break; case 'win-min': // 接收最小化命令 mainWindow.minimize(); break; case 'win-max': // 接收最大化命令 if(mainWindow.isMaximized()) { mainWindow.restore(); } else { mainWindow.maximize(); } break; } });
渲染進程代碼git
// renderer.js const ipcRenderer = require('electron').ipcRenderer; // electron 通訊模塊 const remote = require('electron').remote; // electron 主進程與渲染進程通訊模塊 const Menu = remote.Menu; // electron renderer進程的菜單模塊 const dialog = remote.dialog; // electron 對話框模塊 const fs = require('fs'); // 引入 NodeJS 的 fs 模塊 const shell = require('electron').shell; // 讀取保存數據 var data = fs.readFileSync('./data.json'); var myData = JSON.parse(data); var themes = myData.theme; if(themes == 'dark') { document.getElementById('theme_css').href = './styleDark.css'; } else { document.getElementById('theme_css').href = './style.css'; } if(myData.isFull) { ipcRenderer.send('reqaction', 'win-max'); } // 初始化基本參數 let isSave = true; // 初始狀態無需保存 let txtEditor = document.getElementById('txtEditor'); // 獲取文本框對象 let currentFile = null; // 初始狀態無文件路徑 let isQuit = true; // 初始狀態可正常退出 // 右鍵菜單模板 const contextMenuTemplate = [ { label: '返回', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重作', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, //分隔線 { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '複製', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘貼', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '刪除', accelerator: 'CmdOrCtrl+D', role: 'delete' }, { type: 'separator' }, //分隔線 { label: '全選', accelerator: 'CmdOrCtrl+A', role: 'selectall' }, { type: 'separator' }, //分隔線 { label: 'DevTools', accelerator: 'CmdOrCtrl+I', click: function() { remote.getCurrentWindow().openDevTools(); } }, { accelerator: 'CmdOrCtrl+R', role: 'reload' } ]; // 構建右鍵菜單 const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); txtEditor.addEventListener('contextmenu', (e) => { e.preventDefault(); contextMenu.popup(remote.getCurrentWindow()); }); // 右上角窗體操做按鈕 function winCtrlBtn(id) { switch(id) { case 'win_min': // 最小化 ipcRenderer.send('reqaction', 'win-min'); break; case 'win_max': // 最大化 ipcRenderer.send('reqaction', 'win-max'); break; case 'win_close': // 退出 askSaveNeed(); // 保證安全退出 saveWinData(); // 保存窗體數據 if(isQuit) { // 正常退出 ipcRenderer.sendSync('reqaction', 'exit'); } isQuit = true; // 復位正常退出 break; } } // 監聽窗口變化改變放大縮小按鈕的圖標 window.onresize = function () { if(remote.getCurrentWindow().isMaximized()) { document.getElementById('win_max').style.background = "url(images/ctrl-btn.png) no-repeat 0 -60px"; }else { document.getElementById('win_max').style.background = "url(images/ctrl-btn.png) no-repeat 0 -30px"; } } // 檢測編輯器是否有內容更新,統計字數 txtEditor.oninput = (e) => { if (isSave) { document.title += ' *'; document.getElementById("mainTitle").innerHTML = document.title; } isSave = false; // 字數統計 wordsCount(); } // 菜單操做 ipcRenderer.on('action', (event, arg) => { switch(arg) { case 'new': // 新建文檔 askSaveNeed(); initDoc(); break; case 'open': // 打開文檔 askSaveNeed(); openFile(); wordsCount(); break; case 'save': // 保存當前文檔 saveCurrentDoc(); break; case 'save-as': // 另存爲當前文檔 currentFile = null; saveCurrentDoc(); break; case 'exit': // 退出 askSaveNeed(); // 安全退出 saveWinData(); // 保存窗體數據 if(isQuit) { // 正常退出 ipcRenderer.sendSync('reqaction', 'exit'); } isQuit = true; // 復位正常退出 break; } }); // 初始化文檔 function initDoc() { currentFile = null; txtEditor.value = ''; document.title = 'Notepad - Untitled'; document.getElementById("mainTitle").innerHTML = document.title; isSave = true; document.getElementById("txtNum").innerHTML = 0; } // 詢問是否保存命令 function askSaveNeed() { // 檢測是否須要執行保存命令 if (isSave) { return; } // 彈窗類型爲 message const options = { type: 'question', message: '請問是否保存當前文檔?', buttons: [ 'Yes', 'No', 'Cancel'] } // 處理彈窗操做結果 const selection = dialog.showMessageBoxSync(remote.getCurrentWindow(), options); // 按鈕 yes no cansel 分別爲 [0, 1, 2] if (selection == 0) { saveCurrentDoc(); } else if(selection == 1) { console.log('Cancel and Quit!'); } else { // 點擊 cancel 或者關閉彈窗則禁止退出操做 console.log('Cancel and Hold On!'); isQuit = false; // 阻止執行退出 } } // 保存文檔,判斷新文檔or舊文檔 function saveCurrentDoc() { // 新文檔則執行彈窗保存操做 if(!currentFile) { const options = { title: 'Save', filters: [ { name: 'Text Files', extensions: ['txt', 'js', 'html', 'md'] }, { name: 'All Files', extensions: ['*'] } ] } const paths = dialog.showSaveDialogSync(remote.getCurrentWindow(), options); if(paths) { currentFile = paths; } } // 舊文檔直接執行保存操做 if(currentFile) { const txtSave = txtEditor.value; saveText(currentFile, txtSave); isSave = true; document.title = "Notepad - " + currentFile; document.getElementById("mainTitle").innerHTML = document.title; } } // 選擇文檔路徑 function openFile() { // 彈窗類型爲openFile const options = { filters: [ { name: 'Text Files', extensions: ['txt', 'js', 'html', 'md'] }, { name: 'All Files', extensions: ['*'] } ], properties: ['openFile'] } // 處理彈窗結果 const file = dialog.showOpenDialogSync(remote.getCurrentWindow(), options); if(file) { currentFile = file[0]; const txtRead = readText(currentFile); txtEditor.value = txtRead; document.title = 'Notepad - ' + currentFile; document.getElementById("mainTitle").innerHTML = document.title; isSave = true; } } // 執行保存的方法 function saveText( file, text ) { fs.writeFileSync( file, text ); } // 讀取文檔方法 function readText(file) { return fs.readFileSync(file, 'utf8'); } // 字數統計 function wordsCount() { var str = txtEditor.value; sLen = 0; try{ //先將回車換行符作特殊處理 str = str.replace(/(\r\n+|\s+| +)/g,"龘"); //處理英文字符數字,連續字母、數字、英文符號視爲一個單詞 str = str.replace(/[\x00-\xff]/g,"m"); //合併字符m,連續字母、數字、英文符號視爲一個單詞 str = str.replace(/m+/g,"*"); //去掉回車換行符 str = str.replace(/龘+/g,""); //返回字數 sLen = str.length; }catch(e){ console.log(e); } // 刷新當前字數統計值到頁面中 document.getElementById("txtNum").innerHTML = sLen; } // 拖拽讀取文檔 const dragContent = document.querySelector('#txtEditor'); // 阻止 electron 默認事件 dragContent.ondragenter = dragContent.ondragover = dragContent.ondragleave = function() { return false; } // 拖拽事件執行 dragContent.ondrop = function(e) { e.preventDefault(); // 阻止默認事件 askSaveNeed(); currentFile = e.dataTransfer.files[0].path; // 獲取文檔路徑 const txtRead = readText(currentFile); txtEditor.value = txtRead; document.title = 'Notepad - ' + currentFile; document.getElementById("mainTitle").innerHTML = document.title; isSave = true; wordsCount(); } // 主菜單事件 function showList(o) { hideList("dropdown-content" + o.id); document.getElementById("dropdown-" + o.id).classList.toggle("show"); document.getElementById("a").setAttribute("onmousemove","showList(this)"); document.getElementById("b").setAttribute("onmousemove","showList(this)"); document.getElementById("c").setAttribute("onmousemove","showList(this)"); // 判斷點擊背景採用的皮膚顏色 var clickColor; if(themes == 'dark') { clickColor = '#505050'; } else { clickColor = '#d5e9ff'; } // 點擊狀態下背景色固定 if(o.id == 'a') { document.getElementById('a').style.background = clickColor; document.getElementById('b').style.background = ""; document.getElementById('c').style.background = ""; } if(o.id == 'b') { document.getElementById('a').style.background = ""; document.getElementById('b').style.background = clickColor; document.getElementById('c').style.background = ""; } if(o.id == 'c') { document.getElementById('a').style.background = ""; document.getElementById('b').style.background = ""; document.getElementById('c').style.background = clickColor; } } // 主菜單隱藏操做 function hideList(option) { var dropdowns = document.getElementsByClassName("dropdown-content"); for (var i = 0; i < dropdowns.length; i++) { var openDropdown = dropdowns[i]; if (openDropdown.id != option) { if (openDropdown.classList.contains('show')) { openDropdown.classList.remove('show'); } } } } // 主菜單點擊復位操做 window.onclick = function(e) { if (!e.target.matches('.dropbtn')) { hideList(""); document.getElementById("a").setAttribute("onmousemove",""); document.getElementById("b").setAttribute("onmousemove",""); document.getElementById("c").setAttribute("onmousemove",""); document.getElementById("a").style.background = ""; document.getElementById("b").style.background = ""; document.getElementById("c").style.background = ""; } } // 主菜單快捷鍵操做 function hotkey() { var key = window.event.keyCode; var keyCtrl; if((key == 70)&&(event.altKey)) { keyCtrl = document.getElementById("a"); showList(keyCtrl); } if((key == 69)&&(event.altKey)) { keyCtrl = document.getElementById("b"); showList(keyCtrl); } if((key == 72)&&(event.altKey)) { keyCtrl = document.getElementById("c"); showList(keyCtrl); } } document.onkeydown = hotkey; // 主菜單文件操做 function menuClick(arg) { switch(arg) { case 'new': // 新建文檔 askSaveNeed(); initDoc(); break; case 'open': // 打開文檔 askSaveNeed(); openFile(); wordsCount(); break; case 'save': // 保存當前文檔 saveCurrentDoc(); break; case 'save-as': // 另存爲當前文檔 currentFile = null; saveCurrentDoc(); break; } } // 主菜單編輯操做 function docCommand(arg) { switch(arg) { case 'undo': // 返回 document.execCommand('Undo'); break; case 'redo': // 重作 document.execCommand('Redo'); break; case 'cut': // 剪切 document.execCommand('Cut', false, null); break; case 'copy': // 複製 document.execCommand('Copy', false, null); break; case 'paste': // 粘貼 document.execCommand('Paste', false, null); break; case 'delete': // 刪除 document.execCommand('Delete', false, null); break; case 'seletAll': // 全選 document.execCommand('selectAll'); break; } } // 主菜單中關於跳轉 function aboutMe() { shell.openExternal('https://segmentfault.com/u/shaomeng'); } //換膚 function theme() { if(themes == 'normal') { document.getElementById('theme_css').href = './styleDark.css'; themes = 'dark'; } else { document.getElementById('theme_css').href = './style.css'; themes = 'normal'; } } // 保存窗體相關數據 function saveWinData() { // 獲取窗體相關數據 var dF = remote.getCurrentWindow().isMaximized(); var dX = dF == true ? myData.positionX : remote.getCurrentWindow().getPosition()[0]; var dY = dF == true ? myData.positionY : remote.getCurrentWindow().getPosition()[1]; var dWidth = dF == true ? myData.width : remote.getCurrentWindow().getSize()[0]; var dHeight = dF == true ? myData.height : remote.getCurrentWindow().getSize()[1]; // 數據合集 var obj = { "isFull": dF, "positionX": dX, "positionY": dY, "width": dWidth, "height": dHeight, "theme": themes } // 格式化 json 數據 var d = JSON.stringify(obj, null, '\t'); // 寫入文本 fs.writeFileSync('./data.json', d); }
頁面代碼github
<!-- index.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link id="theme_css" rel="stylesheet" href="./style.css" type="text/css" media="screen"/> <title>Notepad</title> </head> <body> <div class="header"> <div class="onTop"></div> <div class="menuList"> <ul> <li class="dropdown"> <p id="a" class="dropbtn" onclick="showList(this)">文件(F)</p> <div class="dropdown-content" id="dropdown-a"> <p onclick="menuClick('new')">新建<span class="keyQ">Ctrl+N</span></p> <p onclick="menuClick('open')">打開<span class="keyQ">Ctrl+O</span></p> <p onclick="menuClick('save')">保存<span class="keyQ">Ctrl+S</span></p> <p onclick="menuClick('save-as')" class="menuS">另存爲... <span class="keyQ">Ctrl+Shift+S</span></p> <p onclick="winCtrlBtn('win_close')">退出<span class="keyQ">Ctrl+S</span></p> </div> </li> <li class="dropdown"> <p id="b" class="dropbtn" onclick="showList(this)">編輯(E)</p> <div class="dropdown-content" id="dropdown-b"> <p onclick="docCommand('undo')">返回<span class="keyQ">Ctrl+Z</span></p> <p onclick="docCommand('redo')" class="menuS">重作<span class="keyQ">Ctrl+Y</span></p> <p onclick="docCommand('cut')">剪切<span class="keyQ">Ctrl+X</span></p> <p onclick="docCommand('copy')">複製<span class="keyQ">Ctrl+C</span></p> <p onclick="docCommand('paste')">粘貼<span class="keyQ">Ctrl+V</span></p> <p onclick="docCommand('delete')" class="menuS">刪除<span class="keyQ">Ctrl+D</span></p> <p onclick="docCommand('seletAll')">全選<span class="keyQ">Ctrl+A</span></p> </div> </li> <li clcass="dropdown"> <p id="c" class="dropbtn" onclick="showList(this)">幫助(H)</p> <div class="dropdown-content" id="dropdown-c"> <p onclick="theme()">換膚</p> <p onclick="aboutMe()">關於...</p> </div> </li> </ul> </div> <div id="mainTitle" class="mainTitle">Notepad</div> <div class="ctrlBtn"> <p id="win_min" class="win_min" onclick="winCtrlBtn('win_min')"></p> <p id="win_max" class="win_max" onclick="winCtrlBtn('win_max')"></p> <p id="win_close" class="win_close" onclick="winCtrlBtn('win_close')"></p> </div> </div> <div class="txtBox"><textarea class="txtEditor" id="txtEditor"></textarea></div> <div class="bottom">字數:<span class="txtNum" id="txtNum">0</span></div> <script src="./renderer.js"></script> </body> </html>
CSS樣式(白色)web
/*style.css*/ body, html { margin:0; padding:0; height: 100%; overflow: hidden; } .txtBox { width: 100%; height: 100%; position: absolute; top: 30px; padding-bottom: 50px; box-sizing: border-box; } .txtEditor{ width: 100%; height: 100%; font-size: 16px; resize:none; outline:none; border:0px; box-sizing: border-box; cursor:auto; overflow-y:scroll; } .txtEditor:focus{ border:0px; outline:none; } .bottom { height: 19px; width: 100%; font-size: 12px; color: #666666; text-align: right; position: absolute; bottom: 0; border-top: 1px solid #cccccc; background-color: #f2f2f2; } .txtNum { padding-right: 20px; } .header { -webkit-user-select: none; -webkit-app-region: drag; height: 29px; width: 100%; background: #ffffff url(images/logo-24.svg) no-repeat 2px 2px; border-bottom: 1px solid #cccccc; position: absolute; top: 0; z-index:1; } /*Menu************************************************/ .menuList { width: 210px; margin: 0; padding: 0; float: left; display: block; position: absolute; left: 0; top: 0; } ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; line-height: 28px; font-size: 14px; margin-left: 34px; } li { float: left; -webkit-user-select: none; -webkit-app-region: no-drag; } li p, .dropbtn { display: inline-block; color: #000000; text-align: center; padding: 1px 6px; text-decoration: none; margin: 0; } li p:hover, .dropdown:hover .dropbtn { background-color: #d5e9ff; } li.dropdown { display: inline-block; } .dropdown-content { display: none; position: absolute; background-color: #fafafa; min-width: 120px; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.3); } .dropdown-content p { color: #000000; padding: 1px 16px; margin: 0; text-decoration: none; display: block; text-align: left; } .dropdown-content p:hover { background-color: #d5e9ff; } .show { display: block; } .keyQ{ float: right; padding-left: 10px; font-size: 13px; color: #707070; } .menuS{ border-bottom: 1px solid #dbdbdb; } .ctrlBtn { -webkit-user-select: none; -webkit-app-region: no-drag; height: 29px; width: 120px; display: block; position: absolute; right: 0; top: 0; } .ctrlBtn p { width: 40px; height: 29px; float: left; margin: 0; padding: 0; line-height: 29px; display: block; } .win_min { background: url(images/ctrl-btn.png) no-repeat 0 0; } .ctrlBtn p:hover { background-color: #d5e9ff !important; } .win_max { background: url(images/ctrl-btn.png) no-repeat 0 -30px; } .win_close { background: url(images/ctrl-btn.png) no-repeat 0 -90px; } #win_close:hover { background: #cb2c2c url(images/ctrl-btn.png) no-repeat -40px -90px !important; } .mainTitle { height: 29px; font-size: 13px; line-height: 30px; margin-left: 210px; margin-right: 120px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; top: 0; } .onTop { -webkit-user-select: none; -webkit-app-region: no-drag; height: 2px; width: 100%; position: absolute; top: 0; z-index: 1; }
CSS樣式(黑色)shell
/*styleDark.css*/ body, html { margin:0; padding:0; height: 100%; overflow: hidden; } .txtBox { width: 100%; height: 100%; position: absolute; top: 30px; padding-bottom: 50px; box-sizing: border-box; } .txtEditor { width: 100%; height: 100%; font-size: 16px; resize:none; outline:none; border:0px; box-sizing: border-box; cursor:auto; overflow-y:scroll; background-color: #252525; color: #c8c8c8; } .txtEditor:focus{ border:0px; outline:none; } .txtEditor::-webkit-scrollbar {/*滾動條總體樣式*/ width: 18px; /*高寬分別對應橫豎滾動條的尺寸*/ height: 1px; } .txtEditor::-webkit-scrollbar-thumb {/*滾動條裏面小方塊*/ background: #353535; } .txtEditor::-webkit-scrollbar-track {/*滾動條裏面軌道*/ background: #252525; border-left: solid 1px #333333; } .bottom { height: 19px; width: 100%; font-size: 12px; color: #999999; text-align: right; position: absolute; bottom: 0; border-top: 1px solid #3c3c3c; background-color: #3c3c3c; } .txtNum { padding-right: 20px; } .header { -webkit-user-select: none; -webkit-app-region: drag; height: 29px; width: 100%; background: #3c3c3c url(images/logo-24.svg) no-repeat 2px 2px; border-bottom: 1px solid #3c3c3c; position: absolute; top: 0; z-index:1; } /*Menu************************************************/ .menuList { width: 210px; margin: 0; padding: 0; float: left; display: block; position: absolute; left: 0; top: 0; } ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; line-height: 28px; font-size: 14px; margin-left: 34px; } li { float: left; -webkit-user-select: none; -webkit-app-region: no-drag; } li p, .dropbtn { display: inline-block; color: #cccccc; text-align: center; padding: 1px 6px; text-decoration: none; margin: 0; } li p:hover, .dropdown:hover .dropbtn { background-color: #505050; } li.dropdown { display: inline-block; } .dropdown-content { display: none; position: absolute; background-color: #333333; min-width: 120px; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.3); } .dropdown-content p { color: #cccccc; padding: 1px 16px; margin: 0; text-decoration: none; display: block; text-align: left; } .dropdown-content p:hover { background-color: #505050; } .show { display: block; } .keyQ{ float: right; padding-left: 10px; font-size: 13px; color: #999999; } .menuS{ border-bottom: 1px solid #444444; } .ctrlBtn { -webkit-user-select: none; -webkit-app-region: no-drag; height: 29px; width: 120px; display: block; position: absolute; right: 0; top: 0; } .ctrlBtn p { width: 40px; height: 29px; float: left; margin: 0; padding: 0; line-height: 29px; display: block; } .win_min { background: url(images/ctrl-btn.png) no-repeat 0 0; } .ctrlBtn p:hover { background-color: #505050 !important; } .win_max { background: url(images/ctrl-btn.png) no-repeat 0 -30px; } .win_close { background: url(images/ctrl-btn.png) no-repeat 0 -90px; } #win_close:hover { background: #cb2c2c url(images/ctrl-btn.png) no-repeat -40px -90px !important; } .mainTitle { height: 29px; font-size: 13px; line-height: 30px; margin-left: 210px; margin-right: 120px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; top: 0; color: #cccccc; } .onTop { -webkit-user-select: none; -webkit-app-region: no-drag; height: 2px; width: 100%; position: absolute; top: 0; z-index: 1; }
GitHub 源碼https://github.com/mongsel/Simple-Notepadjson