背景
今天已经初六了,想必各位大佬跟我一样也即将踏上工作的路途了。小弟在这里祝愿各位大佬在新的一年,技术进步,事业有成,财源滚滚。
书接上文,过年前在公司由于不想写业务而做了一个小demo(不了解的大佬可以看一下上次都做了什么当春节前不想写业务的前端会做什么 - 掘金 (juejin.cn)),但是上次完成的仅仅是个小玩具甚至他都不怎么好玩,这次我们来继续深入完成更多有意思的功能。 以下是Momo的简单介绍:
- 名称: Momo
- 性别: 不详
- 情绪: 默认/生气
- 状态: Loading/working
- 自述: 大家好我是Momo,诞生于2024年,我是一名智能桌面助手哦。
Momo生活照
完善页面
添加阴影
由于2d的桌宠看起来像一张图片没那么多立体感,这里决定加一个阴影。
html
<style>
.toyarMomo {
...
box-shadow: 9px 6px 16px 3px rgba(0, 0, 0, 0.2);
...
}
</style>
增加耳朵亮闪闪效果
html
<style>
.momo-left_Ear::before,
.momo-right_Ear::before {
content: "";
position: absolute;
inset: 0;
background: url('./src/star.awebp');
border-radius: 44% 49% 22% 65% / 100% 100% 0% 0%;
/* mix-blend-mode: color-dodge; */
/* mix-blend-mode: hard-light; */
/* mix-blend-mode: multiply; */
mix-blend-mode: difference;
}
</style>
效果: 解释一下 这里使用了一张gif动图就是下面这个星星闪闪的图片,并且作为耳朵的伪元素背景,最后使用css,mix-blend-mode: difference;
这个css的意思是该元素的内容应该与元素的直系父元素的内容和元素的背景如何混合。不了解的可以查看这个链接学习mix-blend-mode - CSS:层叠样式表 | MDN (mozilla.org)
ps:我倒是没觉得很好看,不过炫酷就完了
脸部反射光
上面也提到过我觉得2d不够立体,这里再加一个面部灯光反射
html
<style>
.momoBody::after {
content: "";
position: absolute;
inset: 0;
border-radius: 55% 45% 77% 23% / 54% 22% 78% 46%;
background: linear-gradient(90deg,
transparent 0%,
rgba(255, 255, 255, 0.05) var(--per),
rgba(255, 255, 255, 0.1) var(--per),
rgba(255, 255, 255, 0.3) calc(var(--per) + 15%),
rgba(255, 255, 255, .1) calc(var(--per) + 30%),
rgba(255, 255, 255, 0.05) var(--per),
transparent 100%);
mix-blend-mode: color-dodge;
z-index: 999;
}
</style>
<script>
setInterval(() => {
if (flag) {
data += 1
} else {
data -= 1
}
if (data === 30 || data === -50) {
flag = !flag
}
this.body.style.setProperty('--per', data + '%')
}, 30)
</script>
效果:
解释一下,这里使用了一个线性渐变背景作为伪元素的背景,然后线性渐变是一个透明到白色再到透明的效果,然后定时器修改css属性改变这里渐变的位置
为什么不用animation?,当时想着做别的东西来着,后来写完了懒得改了,不过这个也挺机械的感觉不太好(后面优化吧)
使用Electron
介绍
众所周知,htmlcssjs 通常只能运行再浏览器中,但是作为一个桌宠只能运行在浏览器中是不是有点说不太过去,所以这里使用Electron。
安装
-
首先得有node环境这里不演示了
-
初始化package.json npm init -y
-
安装electron
npm install electron --save-dev
注意这里npm安装electron 可能会失败,大概是镜像原因,解决方案 npm安装Electron 项目失败报错问题和解决办法_npm install electron报错-CSDN博客
-
安装forge npm install --save-dev @electron-forge/cli
执行 npx electron-forge import
安装后的项目结构
创建窗口与加载页面
- 创建main.js 并修改package.json中入口js名称
- 创建一个html文件
- 在main.js中创建窗口文件
运行 npm run start 查看代码是否生效 看到窗口证明可以了
最后将写的html 代码移植过来然后在npm run start 看一下,可以看到这里也运行起来了
窗口配置
前面虽然能运行但是白色的边框根本不像桌宠而且这里鼠标事件也不能穿透,这里需要一些配置
可以看到这里已经十分接近桌宠了,但是有个致命问题,就是要么鼠标事件穿透不过去,要么就是能穿透但是点击不了桌宠了,这样也没法交互了
效果
使用ipc通信解决鼠标事件问题
解释一下,这里当鼠标移入桌宠时候则开始鼠标事件监听触发,当鼠标移出桌宠时候则关闭鼠标事件监听触发。
ipc通信这里就不解释了
桌宠的交互功能(1)
html
<style>
.controller {
position: absolute;
top: 50%;
left: 50%;
width: calc(var(--size) * 8/15);
height: calc(var(--size) * 8/15);
transform: translate(-50%, -50%);
z-index: -10;
transition: all 2s;
}
.ctlItem {
position: absolute;
width: calc(var(--size) * 8/15);
height: calc(var(--size) * 8/15);
z-index: 1;
transition: transform 2s;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 6px rgba(255, 255, 255, 0.2);
background-color: rgba(255, 255, 255, 0.4);
display: flex;
align-items: center;
justify-content: center;
}
.ctlItem>.empty {
background-image: url('./src/asstes/add.svg');
background-size: 100% 100%;
}
.ctlItem>.ctlItem_close{
width: calc(var(--size) * 2/15);
height: calc(var(--size) * 2/15);
position: absolute;
/* right: calc(var(--size) * -1/15);
top: calc(var(--size) * -1/15); */
top: calc(var(--size) * 1/15);
right: calc(var(--size) * 1/15);
background-image: url('./src/asstes/close.svg');
background-size: 100% 100% !important;
}
.ctlItem>.ctlItem_close:hover{
background-image: url('./src/asstes/closeHover.svg');
}
.controller.active>.ctlItem {
transform: rotate(calc(var(--ctlItem) + 0deg)) translate(0px, calc(var(--size) * -5/3));
}
.controller.active>.ctlItem>div {
transform: rotate(calc(var(--ctlItem) * -1deg));
}
.ctlItem>div {
width: calc(var(--size) * 5/15);
height: calc(var(--size) * 5/15);
background-size: 100% 100% !important;
}
.ctlItem:hover {
box-shadow: 0 0 6px rgba(255, 255, 255, 0.2);
background-color: rgba(255, 255, 255, 0.7);
cursor: pointer;
}
</style>
<script>
class toyarMomo {
...
createBtn(item, i) {
const ctlItem = document.createElement('div');
const head = 'momo_'
ctlItem.className = 'ctlItem'
ctlItem.style.setProperty('--ctlItem', item + 'deg')
const {path,imgURL} = JSON.parse(localStorage.getItem(head + i)) ||{
path:'',
imgURL:""
}
const src = path || 'empty'
if (src === 'empty') {
ctlItem.innerHTML = `<div class="empty" > </div>`
ctlItem.setAttribute('fileUrl', '')
} else {
ctlItem.innerHTML = `
<div style="background:url(${imgURL})"> </div>
`
this.createCloseBtn(ctlItem,i)
// <div class="ctlItem_close" onClick="closectlItem(${i})"></div>
ctlItem.setAttribute('fileUrl', src)
}
ctlItem.addEventListener('click', async () => {
if (window.versions) {
if (ctlItem.getAttribute('fileUrl')) {
window.versions.ctlItemClick({
type: ctlItem.getAttribute('fileUrl'),
index: i
})
return
}
let {path,imgURL} = await window.versions.ctlItemClick({
type: 'empty',
index: i
})
localStorage.setItem(head + i, JSON.stringify({
path,
imgURL
}))
ctlItem.setAttribute('fileUrl', path)
ctlItem.innerHTML = `
<div
style="
background:url(${imgURL})"
>
</div>
`
this.createCloseBtn(ctlItem,i)
// <div class="ctlItem_close">x</div>
}
})
return ctlItem
}
createController(max = 8) {
const controller = document.createElement('div');
this.controller = controller
controller.className = 'controller'
let route = 360 / max
for (let i = 0; i < max; i++) {
controller.append(this.createBtn(i * route, i))
}
return controller
}
createRoot() {
...
main.addEventListener('dblclick', () => {
this.controller.classList.toggle('active')
})
const controller = this.createController()
main.append(controller)
...
}
}
</script>
html
<script>
//主进程
ipcMain.handle('ctlItemClick',async (event,{type,index})=>{
if(type === 'empty'){
const {filePaths} = await dialog.showOpenDialog({
properties: ['openFile'],
buttonLabel:'选择',
title:'快捷方式',
filters:[
{'name':'应用',extensions:['exe']},
{'name':'图片文件',extensions:['ico','png','jpg','jpeg','svg']},
// {'name':'媒体文件',extensions:['avi','mp4','mp3']},
]
})
const fileIcon = await app.getFileIcon(filePaths[0]);
let imgURL= fileIcon.toDataURL()
console.log(imgURL);
return {imgURL,path:filePaths}
}else{
shell.openPath(type)
}
})
</script>
双击效果
添加删除与启动应用效果