一个小小的记事本,除了基本的功能外,若是须要在用户体验方面作的更好,不少小细节须要进行考虑,如关闭前自动保存窗体信息,保存皮肤设置,以及快捷键功能等等。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