LnEoi

LnEoi 查看完整档案

厦门编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

_
| |__ __ _
| '_ | | | |/ _` |
| |_) | |_| | (_| |
|_.__/ \__,_|\__, |

         |___/  

个人动态

LnEoi 发布了文章 · 1月12日

一个获取按键展示快捷键效果的Input组件

遇到一个需求,页面内要自定义快捷键,这就需要可以有地方设置和展示快捷键,找了一圈Element UI发现没有能稍微改改就能用的组件,所以自己动手写了一个。
这个只有快捷键展示功能,快捷键实际绑定生效的话是依赖传回的快捷键数据,由另外的组件处理的。目前只测试了Chrome的环境。

效果如下:
image

关键点

虽然看起来像是一个Input但在组件内实际上是展示一个标签效果,还需要有删除按钮。这就得在输入框内放下html代码,浏览器的Input组件显然不适合,这就只能自己仿一个类Input组件效果了。

focus、blur、选中高亮效果

Input这类组件是没有focusblur选中高亮效果这些效果的,还好浏览器有预留实现方式,网上也早已有网友提供方案,在div里加上tabindex="0"属性,就能让div获得这些效果。

tabindex属性规定了Tab按键的顺序,写0的话是会按组件默认顺序被选中的,如果写-1则始终无法被选中。因为本身是仿Input组件形式,能被Tab获取也刚好很合理。

然后加上CSS的获取焦点的边框效果、鼠标移动到此显示文本类型指针

.shortcut-key-input {
  cursor: text;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.shortcut-key-input:focus {
  border-color: #188cff;
  box-shadow: 0 0 4px rgba(24, 140, 255, 0.38);
}

文本提示

当没有内容时需要跟Input一样,可以默认显示文本提示。这也是放一个div在里面,用Vue控制,如果输出的标签变量有数据时,就不让此元素显示。

光标闪动效果

这个比较好处理,在类Input里面放一个伪元素,当获取焦点的时候添加此伪元素,然后再给此元素一个CSS3的动画,就有光标闪动的效果了。

@keyframes Blink {
  0% { opacity: 0; }
  100% { opacity: 1; }
}
.shortcut-key-input.cursor::after {
  content: "|";
  animation: Blink 1.2s ease 0s infinite;
  font-size: 18px;
  position: absolute;
  top: 1px;
  left: 8px;
}

按键捕获

按键捕获主要靠keydown事件,其中传回的event里会标记是否按下altctrl(control)等信息,所以做组合按键依赖此信息就可以实现。
因为每次按键都会触发事件,所以要屏蔽掉功能键的事件。代码只实现了一个非功能键的组合,需要多功能键可以另外建立变量判断连续按键的情况然后处理。

    handleKeydown(e) {
      const { altKey, ctrlKey, shiftKey, key, code } = e;
      if (!CODE_CONTROL.includes(key)) {
        if (!this.keyRange.includes(code)) return;
        let controlKey = "";
        [
          { key: altKey, text: "Alt" },
          { key: ctrlKey, text: "Ctrl" },
          { key: shiftKey, text: "Shift" }
        ].forEach(curKey => {
          if (curKey.key) {
            if (controlKey) controlKey += "+";
            controlKey += curKey.text;
          }
        });
        if (key) {
          if (controlKey) controlKey += "+";
          controlKey += key.toUpperCase();
        }
        this.addHotkey({ text: controlKey, controlKey: { altKey, ctrlKey, shiftKey, key, code } });
      }
      e.preventDefault();
    },

CODE_CONTROL是另外预设的按键code码集合,方便处理。本来用的是keyCode的,但keyCode已经被废弃了,推荐的是code
addHotkey就是添加到相应变量的函数,其中主要出判断一下是否有重复的快捷键。
然后预留了一个外部验证的接口,为了多快捷键的时候可以判断是否有重复。
还有一个max接口,可以限制每个组件的快捷键个数。

    addHotkey(data) {
      if (this.list.length && this.list.some(item => data.text === item.text)) return;
      if (this.list.length && this.list.length.toString() === this.max.toString()) return;
      if (!this.verify(data)) return;
      this.list.push(data);
    }

在线预览

https://codesandbox.io/s/vue-hotkeyinput-90m2k

查看原文

赞 0 收藏 0 评论 0

LnEoi 赞了回答 · 1月9日

解决vue怎么给div绑定blur事件

我也遇到了一样的问题,说一下解决思路:

  1. 要把这个元素设置成 focusable 的元素,通过设置 tabindex 这一属性来实现
  2. 我最初用的事件监听是 @focus.native,并不可行,后来试了 @focus.native.capture 也不可行,倒是 @blur@focusout 可行
    blurfoucsout 的区别就是 blur 不支持冒泡,而 Vue 的事件默认触发阶段是在冒泡阶段

另外,题主把 tabindex 错写成 tabIndex 了。

关注 10 回答 10

LnEoi 发布了文章 · 2020-12-21

前端不用Electron也能写Win应用 动手实现一个SwitchHosts程序

说到前端写Windows应用程序,首先想到的一定是Electron,这次我们换一个小众点的工具aardioaardio是一个很轻量的工具,有着自己的语法,面向windows应用开发,刚好其中也有支持前端应用打包功能。

选型

aardio中涉及到HTML+CSS+JS的功能。有Chrome AppElectron,同时还有Sciter.jsHTMLayout两个UI引擎。
一下子就有四种选项,这里按自己摸索的简单总结一下,可能会不太准确:

Chrome App

默认打包出来的应用程序不携带浏览器,程序会启动调用本地的浏览器渲染(不限于官方的ChromeEdge以及国产主流Chromium内核的浏览器都行),如果都没有会自动安装微软的Edge(Chromium版)浏览器。
优势是生成体积比较小(但外部环境不稳定,产生的显示Bug可能性也更多),基础大小估计在800KB左右(经UPX压缩过),前端项目迁移起来不用过多修改就能跑起来。如果要指定环境,也可以在启动代码内指定浏览器路径。

Electron

可以构建Electron的应用,Electron本身就是做桌面程序的为什么还需要使用aardio
看了一下,除了可以利用aardio的一些内置的方便功能函数,同时生成的应用体积也是差异很大,软件自带的示例Electron项目生成文件大概在800KB左右(经UPX压缩过),作者可能使用了精简版的内核替换了原本的程序。如果只做Windows平台应用,这样的打包体积还是很吸引人的。

Sciter.js

这主要是面向桌面开发的一个UI渲染引擎,可以利用HTML+CSS写界面,将浏览器许多无用功能剔除,只留下UI引擎常用的功能,使得体积尽量小巧。
原本以为能将整个前端项目打包进去,但尝试了一下,好像不行。HTMLCSS是单独传入的,没办法像是前端那么自由的引用。使用逻辑更像是一个html界面就配置一个Sciter.js对象,而不是将html程序的所有逻辑放到里面去执行。
生成的应用体积也是比较小的,大概在2.8MB左右(经UPX压缩过)。看介绍,大部分常用的CSS支持,同时也有一些特殊的语法方便处理界面。

HTMLayout

HTMLayoutSciter.js的前身,程序体积更小,但支持的CSS功能也更加有限。同样也不太适用前端项目的打包。
对比了一下HTMLayoutSciter.js生成应用的体积,应用大概在1.5MB左右(经UPX压缩过),两者只差了1.3MB左右,但放弃的新语法可能要很多,所以这个大部分场景可以不必考虑了。

尝试一下

官方有推过一个修改版的hostsSwitchHelper,使用的就是Chrome App模式,既然是尝试,那就先选这个模式来试试。

Chrome App模式其实很小白,如果没有与系统交互的功能,那么几乎就是直接将前端项目编译,然后导入到开发工具中就能生成一个应用。

先来看看界面:
界面
比较传统的IDE界面,左上角的Logo点开,然后点击新建工程,能看到新建窗口。
新建项目
选择Web界面栏目,然后选择第一项Chrome App,设置好目录就能创建出一个Chrome App例子了,其中自带已经能跑起来的代码。
代码量很少,不熟悉的可以直接在这个项目上做简单修改,就是一个自己的项目了。

做一个自己的程序

这是已完成的界面:

image

前端部分

先从熟悉的开始吧,把前端部分先完成。

由于程序不大,于是不使用Vue或者React了,找了个原生漂亮的UI框架LuLu UI,配合TypeScript,打包工具也不要太重的WebPack,我们选用更加轻量的parcel

其实一开始是直接用LuLu UI+原生JS,简单的写几个页面,后来对界面细节不断调整(这块反复修改花了不少时间),以及操作逻辑的优化,总体逻辑相对复杂了起来,于是引入了打包工具,将CSS换成了LESSJavaScript换成了TypeScript,并且分出了目录结构出来。

打包其实只需要编译完成的前端文件,所以前端工程可以随意放在哪儿都行,最后只需要把编译出的文件放到aardio设置的目录下就可以了。

至于前端就很普通,常见的建立项目,补充样式与逻辑,加上parcel本身也很轻量易用,太细节的就不多说了,挑一些主要的说。

关闭翻译提示

如果不设置,网页默认是英文,打开后Chrome会弹出翻译提示,这个功能对于本地应用程序无意义。设置的话就是标注网页语言为中文,或是简单粗暴在head标签里加上这句,禁止使用翻译功能。

<meta name="google" content="notranslate" />
屏蔽影响使用的快捷键

网页的F12Ctrl+Shift+iCtrl+u等,万一触碰到会影响应用程序体验,用JS代码将这些快捷键禁用。
由于KeyboardEvent.keyCode已经被废弃了,这里直接使用KeyboardEvent.key判断

  window.onkeydown = window.onkeyup = window.onkeypress = function (event: KeyboardEvent) {
    //禁止ctrl+u
    if (event.ctrlKey && event.key.toLowerCase() == 'u') return false;
    //禁止ctrl+s
    if (event.ctrlKey && event.key.toLowerCase() == 's') return false;
    //禁止ctrl+shift+i
    if (event.ctrlKey && event.shiftKey && event.key.toLowerCase() == 'i') return false;
    //禁止 F5
    if (event.key == 'F5') return false;
    //禁止 F12
    if (event.key == 'F12') return false;
  };
屏蔽右键

右键的菜单也有无关动东西,影响使用体验,重点还很难看。如果没有右键功能可以直接屏蔽,这里为了方便加上了右键功能,所以重画了右键菜单键,替换了原生的右键。

  window.oncontextmenu = function (event: MouseEvent) {
    event.preventDefault();
    show(event); // 里面包括打开自己的菜单层
    return false;
  };
  
  document.addEventListener('click', close); // 关闭右键菜单 close函数里就是将相应的层隐藏

show的逻辑很简单,获取当前鼠标位置,然后判断窗口是否会溢出,做相应的高度修改,最后将指定的菜单层显示。

/**
 * 显示右键菜单
 * @param {MouseEvent} event
 */
function show(event: MouseEvent) {
  if (!isNavItem(event)) return;
  // 获取当前鼠标位置
  let mouseX = event.clientX;
  let mouseY = event.clientY;
  // 判断边界值,防止菜单栏溢出可视窗口
  const winHeight = document.documentElement.clientHeight ?? document.body.clientHeight;
  // BUS.contextMenu是存储的前端菜单DOM
  if (mouseY > winHeight - BUS.contextMenu.offsetHeight) mouseY = winHeight - BUS.contextMenu.offsetHeight - 10;
  else mouseY = mouseY;

  BUS.contextMenu.style.left = `${mouseX}px`;
  BUS.contextMenu.style.top = `${mouseY}px`;
}

这里增加了一些细节判断,像是编辑器区域不弹出右键,在标签项右键与导航空白区右键显示不同菜单。

调试模式

由于禁用掉开发者工具在调试的时候很麻烦,通过注释代码屏蔽又容易忘记,parcel打包工具同样提供了环境变量。

process.env.NODE_ENV === 'production'
网页元素重渲染

由于没有使用现代框架,这部分起始逻辑很重。像是鼠标右键不同地方显示不同菜单的功能实现起来就会很麻烦,要不然代码就很冗余。于是引入了小巧快捷的前端模板引擎art-template,一部分操作交给引擎判断,通过添加不同的类名展示不同的状态。

比如鼠标右键点击在标签项上,如果当前的项没有添加url参数,则是本地的Hosts,渲染时会添加disabled类名,标识禁用此元素。disabled类的CSS核心是pointer-events: none;,这样即可让此元素不响应鼠标事件,无需再添加多余的js逻辑。

导航栏的当前选中的类名添加、添加新项等操作全部只是在统一的数据中做记录,然后依赖重新渲染一次性替换掉HTML,避免了写复杂冗长的DOM增删逻辑,也简化了代码耦合。

所以项目里单独做了一个数据管理的模块。从原本的简单nav渲染单文件逻辑开始,到后来由于UI逻辑越做越细致,这个里也从单文件变成了多文件,尽量把外部操作与纯数据操作隔离。

文本编辑器

如果使用textarea标签可以实现多行文本,但没办法提供注释文字的区分显示,也没办法提供行号、快捷键、搜索等扩展功能,所以我们需要一个文本编辑器。

经过筛选比较常见的代码编辑器有CodeMirroraceEditormonacoEditorVS Code的前身),三者中CodeMirror体积最小,我们需要的功能也不要太多,于是就选这个了。

使用方法网上很多,源码中注释也很清楚,使用方式就不多提了。

语法颜色

由于我们的Hosts实际只需要给 #号上色,编辑器有类似的语法,但也会将多余的关键字和结构显示出颜色,这时候我们可以自己修改一个。
简单的翻阅语法源码能发现在tokenBasereturn中的token有对很多信息做匹配,将没有的信息删除,减少判断逻辑,连带的可以删除几个相关函数代码。之后在底部CodeMirror.defineMIME中有自定义许多关键字,对我们也没用,都可以删除。然后记得改一个新名字,这样就实现了一个我们专门的语法,导入插件中就能使用了。

主题样式我们也可以自定义,同样是找一份看的不错的样式,然后把CSS代码复制过来,其中有许多状态的样式名称,根据相应名称修颜色即可。

这里我开启了高亮选中行,所以在当前行会有一个蓝色底色,由于我在编辑器文本框背景上又加了一个当前项的类型图标,会导致图标被覆盖,这时候只需要将div.CodeMirror-selected的颜色调成半通明模式即可,但由于我背景的透明度调的很低(背景透明度是0.06,而选中的透明度是0.8),所以不仔细看的话实际上还是有点像是被覆盖住。

快捷键

本身是做切换标签才自动保存的逻辑,但如果用户修改了文本内容,误以为是实时保存而直接关闭软件,这样就会导致修改丢失。所以需要加上一个当前文本修改过的标识,于是又在右上角加了一个保存按钮(一个不好看,顺便又加上了刷新与更新远程文件按钮)。

有了按钮,自然的会习惯用Ctrl+s去保存文件,好在CodeMirror是提供了自定义快捷键的。

extraKeys: {
  'Ctrl-S': eventTextareaSave, // 这里是自定义的保存逻辑
}
网络请求及代理

常见的应用场景是从网络上复制别人整理好的Hosts信息,所以为了方便增加了从网络获取Hosts功能。但这种一般需要借助代理功能才能获取到,搜索网上并没见到js能直接设置代理的办法。

这时候就应该将前端不易操作的功能放到aardio中,通过inet.http库可以直接发起网络请求,同时默认也会走系统代理设置,完全满足了目前的功能所需。

aardio部分

到这里就进入了我们不是熟悉的部分了。aardio语法很像js,但细节上跟js又有差距,所以写起来的时候很容易弄混,要注意一下。

这里说几个看目前代码需要理解的点:
import,是将对应的命名空间导入当前的命名空间,使用的时候可以直接使用。会按内核库标准库用户库(应用程序lib目录)查询引入。
var,申明变量不一定都要使用var关键字,如果没使用是默认挂在当前命名空间下的。如果使用了,则作用域是在当前代码块。同时申明变量也只有var关键字,不要顺手把ES6的关键字用上了。
global,这是默认的全局空间名字,简写是..在代码中经常看到,比如要操作io库,可以写成..io调用。
tableaardiotable是一种类型,类似js里的数组和对象,两种类型同时是table类型。写法上keyvalue连接是用=,末尾结束是用;,这个与js差异很大,不小心写错会导致报错,复查错觉无时往往容易因为惯性而忽略。
多返回值,程序是支持多返回值的,用,分隔,接收返回也是直接用,分隔写变量即可接收。
目录环境,路径如果是~开头的代表exe应用程序运行的当前目录,如果是在测试环境,则是aardio.exe主程序的目录。

其他不用写大段逻辑的话,基本也能看懂了。

创建chrome程序

main.aardio是程序主入口,在这个文件里写起始逻辑。

import chrome.app; // 导入所需的库
var theApp = chrome.app(); // 创建一个实例
theApp.start(url); // chrome打开指定的url

这样就能启动一个chrome应用了。

js与aardio交互
theApp.external = {
    key = function(){
        // 逻辑
    };
}

external里面传入相应的逻辑,前端通过aardio组件可以实现调用。

引入前端文件

image

在左侧新建“虚拟目录”,右侧会出现设置项,“目录路径”修改成我们前端的路径,“内嵌资源”要选true,不然无法生成单文件的应用程序。

最后对着这个虚拟目录单击右键,选择“同步本地目录”,如果目录内有文件的话会全部显示出来。如果有修改重新生成前端文件的话,也要记得手动同步一次,目录是不会自动刷新的。

编译应用

这样我们的前端项目就能打包成win的应用程序了,按F7就能一键编译,稍微等一儿编译好的应用就生成了,是不是很简单?

Electron版尝试

aardioElectron做了简化,目前的项目可以直接复制到Electron工程的渲染进程中使用,只要启动代码做相应修改即可。但尝试下来,发现UI渲染十分卡顿,不知道是什么原因。目前功能暂时够用,这块等之后有空再了解吧。

结尾

总体使用下来,对于熟悉前端以及前端工具链的形式,aardio的界面和文本编辑器很不适应(VS Code上有人做了一个插件,说是可以直接在VS Code编译,配置了依然报错,而且没有格式化,没有智能提示,反不如原生IDE方便)。
文档也相对比较简单,需要有一定的耐心,很多地方需要自己从右侧的范例中查找例子学习,如果没有例子,则要从左下角的标准库里,看源代码和智能提示了解具体有什么功能和如何编写代码。

项目源码

具体细节有兴趣的话请查阅源码: HostsSwitch
https://github.com/Lnncoco/ho...

查看原文

赞 1 收藏 1 评论 0

LnEoi 关注了专栏 · 2020-11-14

创宇前端

最酷开发者的技术分享,来自知道创宇 FED 开发一线。 技术前端,不只是前端。知道创宇前端团队关注 Web 全栈,折腾各种新与酷的东西。我们追踪日新月异的前端动向,我们思考可视化与交互的艺术,探索大数据、深度学习、区块链等新兴技术,发表关于项目与团队的观点。 创宇前端致力于坚持一种声音,让更多对技术有热爱与好奇心的人能够同行。 敬请优先关注我们的微信公众号创宇前端(KnownsecFED)订阅新消息,交互设计等技术以外的内容不会同步到 SF。

关注 2370

LnEoi 赞了回答 · 2020-11-13

解决JavaScript 数组有内容但 length === 0?

控制台的是快照,点开的时候才会去读取真实内容。

所以你可以深拷贝之后再打印看看。一般直接JSON.parse(JSON.stringify())

关注 4 回答 3

LnEoi 关注了专栏 · 2020-11-09

我们一起进大厂

微信搜索:三太子敖丙

关注 6455

LnEoi 关注了用户 · 2020-10-29

再见尼克 @nichoaslee119

人生几何

关注 37

LnEoi 赞了回答 · 2020-10-22

解决不使用三元运算符和 if ,如何优雅地切换变量的值?

逻辑上 写个函数,用函数调用就清晰明了

一味追求 精简 和 技巧 只能让代码变得难以看懂和维护。

容易维护的代码 才是真的 好代码

所谓技巧高超,最后都变成被人骂的 祖传代码

关注 10 回答 11

LnEoi 赞了回答 · 2020-10-21

vue <keep-alive>如何销毁组件?

了解下include
keep-alive

关注 3 回答 1

LnEoi 关注了用户 · 2020-10-21

小磊哥er @yzsunlei

大前端工程师

关注 11

认证与成就

  • 获得 60 次点赞
  • 获得 31 枚徽章 获得 0 枚金徽章, 获得 8 枚银徽章, 获得 23 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-01-20
个人主页被 1.7k 人浏览