Implement companion styling and animations

This commit is contained in:
Dennis Schoepf 2021-07-19 00:07:13 +02:00
parent ab372d6627
commit 7cec5fa2fb
9 changed files with 236 additions and 1 deletions

View file

@ -0,0 +1,3 @@
<svg width="342" height="296" viewBox="0 0 342 296" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M171 0L341.607 295.5H0.39299L171 0Z" fill="#590C13"/>
</svg>

After

Width:  |  Height:  |  Size: 170 B

View file

@ -0,0 +1,3 @@
<svg width="300" height="275" viewBox="0 0 300 275" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M224.013 25L150 0L75.987 25L26.6449 100L4.9344 175L0 225L16.7763 250L75.9868 266.5L150 275L224.013 266.5L283.224 250L300 225L295.066 175L273.355 100L224.013 25Z" fill="#590C13"/>
</svg>

After

Width:  |  Height:  |  Size: 295 B

View file

@ -6,7 +6,15 @@
<link rel="stylesheet" href="./styles.scss" /> <link rel="stylesheet" href="./styles.scss" />
</head> </head>
<body> <body>
<div class="ui"></div> <div class="ui">
<div id="companion">
<svg xmlns="http://www.w3.org/2000/svg" id="companion-shape" viewBox="0 0 80 80">
<polygon fill="#A60303" points="40,0 68,12 80,40 68,68 40,80 12,68 0,40 12,12"></polygon>
</svg>
<div id="companion-eye"><div id="companion-pupil"></div></div>
<div id="companion-mouth"></div>
</div>
</div>
<script src="./main.ts"></script> <script src="./main.ts"></script>
</body> </body>
</html> </html>

View file

@ -4,6 +4,7 @@ import { LegacyScene } from './src/scenes/LegacyScene';
import { OverviewScene } from './src/scenes/OverviewScene'; import { OverviewScene } from './src/scenes/OverviewScene';
import { Scenes } from './src/scenes/scenes'; import { Scenes } from './src/scenes/scenes';
import store from './src/store'; import store from './src/store';
import { Companion } from './src/ui/companion';
const sketch = (s: p5) => { const sketch = (s: p5) => {
// Scenes // Scenes
@ -14,6 +15,8 @@ const sketch = (s: p5) => {
s.createCanvas(SCREEN_WIDTH, SCREEN_HEIGHT); s.createCanvas(SCREEN_WIDTH, SCREEN_HEIGHT);
s.noCursor(); s.noCursor();
new Companion();
overviewScene = new OverviewScene(); overviewScene = new OverviewScene();
legacyScene = new LegacyScene(); legacyScene = new LegacyScene();
}; };
@ -39,4 +42,5 @@ const sketch = (s: p5) => {
}; };
}; };
// Setup Sketch
export const mp5 = new p5(sketch); export const mp5 = new p5(sketch);

24
package-lock.json generated
View file

@ -9,10 +9,12 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"animejs": "^3.2.1",
"p5": "^1.4.0", "p5": "^1.4.0",
"zustand": "^3.5.7" "zustand": "^3.5.7"
}, },
"devDependencies": { "devDependencies": {
"@types/animejs": "^3.1.4",
"@types/p5": "^1.3.0", "@types/p5": "^1.3.0",
"parcel-bundler": "^1.12.5", "parcel-bundler": "^1.12.5",
"prettier": "^2.3.2", "prettier": "^2.3.2",
@ -1767,6 +1769,12 @@
"node": ">= 6.0.0" "node": ">= 6.0.0"
} }
}, },
"node_modules/@types/animejs": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-3.1.4.tgz",
"integrity": "sha512-WUjeFT2SXd6intfE6cg6eL1jk/JL88JqM2gC4WqO4iHLmbCvHUq6aoLK13lGpDWs4FtS2PHoYraJZ0dEx99Dyg==",
"dev": true
},
"node_modules/@types/p5": { "node_modules/@types/p5": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.3.0.tgz",
@ -1850,6 +1858,11 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true "dev": true
}, },
"node_modules/animejs": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.1.tgz",
"integrity": "sha512-sWno3ugFryK5nhiDm/2BKeFCpZv7vzerWUcUPyAZLDhMek3+S/p418ldZJbJXo5ZUOpfm2kP2XRO4NJcULMy9A=="
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
@ -10415,6 +10428,12 @@
"physical-cpu-count": "^2.0.0" "physical-cpu-count": "^2.0.0"
} }
}, },
"@types/animejs": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-3.1.4.tgz",
"integrity": "sha512-WUjeFT2SXd6intfE6cg6eL1jk/JL88JqM2gC4WqO4iHLmbCvHUq6aoLK13lGpDWs4FtS2PHoYraJZ0dEx99Dyg==",
"dev": true
},
"@types/p5": { "@types/p5": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.3.0.tgz",
@ -10481,6 +10500,11 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true "dev": true
}, },
"animejs": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.1.tgz",
"integrity": "sha512-sWno3ugFryK5nhiDm/2BKeFCpZv7vzerWUcUPyAZLDhMek3+S/p418ldZJbJXo5ZUOpfm2kP2XRO4NJcULMy9A=="
},
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",

View file

@ -11,6 +11,7 @@
"deploy": "./deploy.sh" "deploy": "./deploy.sh"
}, },
"devDependencies": { "devDependencies": {
"@types/animejs": "^3.1.4",
"@types/p5": "^1.3.0", "@types/p5": "^1.3.0",
"parcel-bundler": "^1.12.5", "parcel-bundler": "^1.12.5",
"prettier": "^2.3.2", "prettier": "^2.3.2",
@ -18,6 +19,7 @@
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },
"dependencies": { "dependencies": {
"animejs": "^3.2.1",
"p5": "^1.4.0", "p5": "^1.4.0",
"zustand": "^3.5.7" "zustand": "^3.5.7"
} }

View file

@ -1,12 +1,15 @@
import create from 'zustand/vanilla'; import create from 'zustand/vanilla';
import { Scenes } from './scenes/scenes'; import { Scenes } from './scenes/scenes';
import { CompanionState } from './ui/companion';
export interface State { export interface State {
currentScene: Scenes; currentScene: Scenes;
companionState: CompanionState;
} }
const store = create<State>(() => ({ const store = create<State>(() => ({
currentScene: Scenes.OVERVIEW, currentScene: Scenes.OVERVIEW,
companionState: CompanionState.IDLE,
})); }));
export default store; export default store;

131
src/ui/companion.ts Normal file
View file

@ -0,0 +1,131 @@
import anime from 'animejs/lib/anime.es';
import { reject } from 'core-js/fn/promise';
import store from '../store';
export enum CompanionState {
IDLE = 'IDLE',
AWAIT = 'AWAIT',
ACTIVE = 'ACTIVE',
SUCCESS = 'SUCCESS',
}
export class Companion {
ref: HTMLElement;
hoverAnimation: any;
constructor() {
this.ref = document.getElementById('companion');
this.ref.addEventListener('click', () => this.handleClick());
this.ref.addEventListener('mouseover', () => this.handleMouseEnter());
this.ref.addEventListener('mouseleave', () => this.handleMouseLeave());
store.subscribe(
(companionState) => {
if (companionState === CompanionState.ACTIVE) {
this.showActiveShape();
this.scaleUpCompanion();
this.pupilFollowCursor();
} else if (companionState === CompanionState.IDLE) {
this.scaleDownCompanion();
this.showIdleShape();
} else if (companionState === CompanionState.AWAIT) {
this.playAwaitAnimation();
} else if (companionState === CompanionState.SUCCESS) {
this.playSuccessAnimation();
this.scaleDownCompanion();
this.showIdleShape();
}
},
(state) => state.companionState
);
}
showActiveShape() {
const mouth = document.getElementById('companion-mouth');
mouth.style.opacity = '100%';
anime({
targets: '#companion-shape polygon',
points: [{ value: '40,0 68,12 80,40 80,75 40,80 0,75 0,40 12,12' }],
easing: 'easeOutQuad',
duration: 500,
loop: false,
});
}
showIdleShape() {
const mouth = document.getElementById('companion-mouth');
mouth.style.opacity = '0%';
anime({
targets: '#companion-shape polygon',
points: [{ value: '40,0 68,12 80,40 68,68 40,80 12,68 0,40 12,12' }],
easing: 'easeOutQuad',
duration: 500,
loop: false,
});
}
scaleUpCompanion() {
anime({
targets: '#companion',
scale: 3,
translateX: -15,
translateY: -12,
loop: false,
duration: 700,
});
}
scaleDownCompanion() {
anime({
targets: '#companion',
scale: 1,
loop: false,
translateX: 0,
translateY: 0,
duration: 800,
});
}
playSuccessAnimation() {}
playAwaitAnimation() {}
pupilFollowCursor() {
document.addEventListener('mousemove', (e) => {
const pupil = document.getElementById('companion-pupil');
const pupDim = pupil.getBoundingClientRect();
pupil.style.transform = `translate(${-((pupDim.x - e.pageX) * 0.005)}px, ${-(
(pupDim.y - e.pageY) *
0.015
)}px)`;
});
}
handleClick() {
const { companionState } = store.getState();
let newCompanionState: CompanionState;
if (companionState === CompanionState.ACTIVE) {
newCompanionState = CompanionState.IDLE;
} else {
newCompanionState = CompanionState.ACTIVE;
}
store.setState({ companionState: newCompanionState });
}
handleMouseEnter() {
this.showActiveShape();
}
handleMouseLeave() {
const { companionState } = store.getState();
if (companionState !== CompanionState.ACTIVE) {
this.showIdleShape();
}
}
}

View file

@ -4,6 +4,7 @@ body {
padding: 0; padding: 0;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
position: relative;
} }
main { main {
@ -15,3 +16,59 @@ main {
*::after { *::after {
box-sizing: border-box; box-sizing: border-box;
} }
.ui {
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
display: flex;
justify-content: flex-end;
#companion {
position: relative;
&:hover {
cursor: pointer;
}
#companion-shape {
height: 40px;
width: 40px;
}
#companion-mouth {
position: absolute;
z-index: 2;
bottom: 16%;
left: calc(50% - 15px);
width: 30px;
height: 15px;
border: solid 3px #590c13;
border-color: #590c13 transparent transparent transparent;
border-radius: 70%/15px 15px 0 0;
transform: rotate(180deg);
opacity: 0;
}
#companion-eye {
height: 20px;
width: 20px;
top: 20%;
left: calc(50% - 10px);
position: absolute;
border-radius: 50%;
background-color: #ebeef2;
#companion-pupil {
height: 5px;
width: 5px;
top: 50%;
left: calc(50% - 4px);
position: absolute;
border-radius: 50%;
background-color: #0d0d0d;
}
}
}
}