前言

一个小小的记事本,除了基本的功能外,如果需要在用户体验方面做的更好,很多小细节需要进行考虑,如关闭前自动保存窗体信息,保存皮肤设置,以及快捷键功能等等。

效果图

效果图.gif

主要优化方向

  1. 模拟主菜单(带快捷键)
  2. 换肤
  3. 窗体信息保存

开发思路

默认的主菜单在electron隐藏边框后,依然可以使用快捷键进行操作,在此基础上直接写一个具有点击下拉效果的菜单即可。窗体的长宽、位置、最大化这几个信息获取后利用nodejs的fs模块进行保存,保存为json格式便于读取和调用。换肤功能则采用替换css样式文件。这就是三个主要功能的开发思路,其他小细节在开发中逐步优化。

代码

主进程代码

// 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;
  }
});

渲染进程代码

// 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);
}

页面代码

<!-- 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样式(白色)

/*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样式(黑色)

/*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-Notepad


少孟
41 声望2 粉丝

终生职业————学生。