First post the final effect:

After seeing the effect, paste the code directly:

import { screen } from 'electron'; import ChildWindow from '.. /win/childWin'; import { NOTIFIER } from '.. /.. /config'; import { getOptions } from '.. /.. /utils/dialog'; import { easeInQuad, easeOutQuad } from '.. /common'; export type PopupNotify = { title: string; content: string; }; // export const NOTIFIER = {width: 290, height: 200, url: '/ NOTIFIER ',}; const offset = 8; export default class Notifier { isShown: boolean; winInstance? : ChildWindow; screenHeight: number = 0; private timer: any = null; constructor(args: PopupNotify) { this.isShown = false; this.createNotifier(args); } createNotifier(args: PopupNotify) { let display = screen.getPrimaryDisplay().workAreaSize; let WIDTH = display.width; let HEIGHT = display.height; This. screenHeight = HEIGHT; const winInstance = (this.winInstance = new ChildWindow({ width: NOTIFIER.width, height: NOTIFIER.height, x: WIDTH - NOTIFIER.width - offset, y: HEIGHT, frame: false, resizable: false, movable: false, alwaysOnTop: true, opacity: 0, webPreferences: { devTools: false, }, })); winInstance.bind({ readyToShow: () => { this.show(); }, focus: this.focus, blur: this.blur, closed: this.closed, }); const options = getOptions(NOTIFIER.url, { ... args, min: false, }); winInstance.loadFile(options, 'window'); return winInstance; } show() { this.winInstance && this.winInstance.show(); this.animate(this.winClose); / / animate = (callback: Function, open: Boolean = true) => {let currentTime = 0; this.timer = setInterval(() => { currentTime += 10; if (currentTime > 200) { open && (this.isShown = true); clearInterval(this.timer); /** Start executing the current popover destruction method */ callback(); } else { this.setBounds(Math.floor( open ? easeOutQuad(currentTime, this.screenHeight, NOTIFIER.height + offset, 200, ! open) : easeInQuad(currentTime, this.screenHeight - NOTIFIER.height - offset, NOTIFIER.height + offset, 200))); this.setOpacity(Number(open ? easeInQuad(currentTime, 0, 1, 200, open).toFixed(2) : easeOutQuad(currentTime, 1, 0, 200, open).toFixed(2))); }}, 10); }; setBounds = (y: number) => { try { this.winInstance && this.winInstance.win && ! this.winInstance.win.isDestroyed() && this.winInstance.win.setBounds({ y }); } catch (error) { console.log(error); }}; setOpacity = (opacity: number) => { try { this.winInstance && this.winInstance.win && ! this.winInstance.win.isDestroyed() && this.winInstance.win.setOpacity(opacity); } catch (error) { console.log(error); }}; Focus = () => {/** * After focus, cancel the countdown and close the current window ClearTimeout */ if (this.isshown && this.timer) {clearTimeout(this.timer); clearTimeout(this.timer); this.timer = null; }}; Blur = () => {/** start countdown close current window */ this.timer === null && this.winclose (); }; */ closed = () => {this.timer && clearTimeout(this.timer); }; /** winClose = () => {this.animate() => {this.animate() => {this.winInstance && this.winInstance.win && ! this.winInstance.win.isDestroyed() && this.winInstance.win.close(); }, false); }, 3000); }; /** linear */ export function linear(currentTime: number, startValue: number, changeValue: number, duration: number, increase: boolean = true ) { return increase ? (changeValue * currentTime) / duration + startValue : startValue - (changeValue * currentTime) / duration; } /** ease-in */ export function easeInQuad( currentTime: number, startValue: number, changeValue: number, duration: number, increase: boolean = true ) { currentTime /= duration; return increase ? changeValue * currentTime * currentTime + startValue : startValue - changeValue * currentTime * currentTime; } /** ease-out */ export function easeOutQuad( currentTime: number, startValue: number, changeValue: number, duration: number, increase: boolean = true ): number { currentTime /= duration; return increase ? -changeValue * currentTime * (currentTime - 2) + startValue : startValue - -changeValue * currentTime * (currentTime - 2); }Copy the code

For speed curves, check out this article: Easing functions

/ / ChildWindow is actually for a package in the process of new BrowserWindow import {BrowserWindow BrowserWindowConstructorOptions, app } from 'electron'; import { LoadFileOption } from 'app/utils/dialog'; const merge = require('lodash/merge'); const path = require('path'); const os = require('os'); export interface WindowListener { readyToShow? : () => void; shown? : () => void; finish? : () => void; closed? : () => void; focus? : () => void; blur? : () => void; } export default class ChildWindow { win: BrowserWindow | IAnyObject; winOptions: BrowserWindowConstructorOptions; constructor(args: BrowserWindowConstructorOptions) { this.win = {}; this.winOptions = args; this.createMainWindow(); } createMainWindow = () => { this.win = new BrowserWindow( merge( { center: true, // frame: false, show: false, resizable: false, transparent: false, webPreferences: { nodeIntegration: true, }, }, this.winOptions ) ); If (OS) platform () = = = 'win32') {/ * * under Windows disable right click * / this. Win. HookWindowMessage (278, (e: Event) => { this.win.setEnabled(false); setTimeout(() => this.win.setEnabled(true), 100); return; }); }}; bind = (cb? : WindowListener) => { if (! cb) return; cb.readyToShow && this.win.once('ready-to-show', () => { cb.readyToShow! (a); }); cb.shown && this.win.once('show', () => { cb.shown! (a); }); cb.focus && this.win.on('focus', () => { cb.focus! (a); }); cb.blur && this.win.on('blur', () => { cb.blur! (a); }); cb.closed && this.win.once('closed', () => { cb.closed! (a); }); cb.finish && this.win.webContents.once('did-finish-load', () => { cb.finish! (a); }); }; loadURL = (url: string) => { const userAgent = this.win.webContents.getUserAgent(); this.win.loadURL(url, { userAgent }); }; loadFile = (options: LoadFileOption, temp: string = 'index') => { this.win.loadFile( app.isPackaged ? path.resolve(__dirname, `pages/${temp}.html`) : path.resolve(__dirname, `.. /.. /pages/${temp}.html`), { ... options, } ); }; getWebContents = () => { return this.win ? this.win.webContents : null; }; getUrl = () => { const webContents = this.win.webContents; if (! webContents || webContents.isDestroyed() || webContents.isCrashed()) return ''; return webContents.getURL(); }; show = () => { this.win.show(); }; focus = () => { this.win.focus(); }; isVisible = () => { return this.win.isVisible(); }; isDestroyed = () => { return this.win.isDestroyed(); }; isMinimized = () => { return this.win.isMinimized(); }; restore = () => { this.win.restore(); }; mini = () => { this.win.minimize(); }; hide = () => { this.win.hide(); }; max = () => { this.win.maximize(); }; reload = () => { this.win.reload(); }; close = () => { this.win.close(); }; }Copy the code

The implementation principle is actually very simple, mainly using the Electron browserWindow setBounds and setOpacity methods, Then use screen.getprimaryDisplay ().workareasize to get the width and height of the screen. By default, the window is placed at the bottom right corner of the screen and its height is offset downward. When the readyToShow event is triggered by the popup, the transition animation starts.

The last

The source code is actually in my project: the persistence of local data in the electron_client project uses SQLite, and then uses the Sequelize database model library.

I also wrote a react SSR scaffolding tool: js-react- SSR -cli

Already realized project DOM, routing, store and CSS isomorphism, development environment hot update function

Supports js and typescript, MobX and Redux

The default CSS preprocessor is stylus

Welcome to use it.