Finished targetPreview

Reverted back to react logic instead of scss logic and improved performance by partially showing the preview icon instead of letting it follow the mouse directly.
This commit is contained in:
aronmal 2022-08-14 02:37:58 +02:00
parent c837e0df2f
commit 83f187a7dc
Signed by: aronmal
GPG key ID: 816B7707426FC612
4 changed files with 149 additions and 105 deletions

View file

@ -1,12 +1,23 @@
import { faBurst, faCrosshairs, faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CSSProperties, useState } from 'react';
import { CSSProperties, useEffect, useState } from 'react';
import { TargetPreviewType } from './interfaces';
import './styles/App.scss';
function App() {
const [target, setTarget] = useState({
event: false,
show: false,
x: 0,
y: 0
})
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>({
newX: 0,
newY: 0,
shouldShow: false,
appearOK: false,
eventReady: true,
show: false,
x: 0,
y: 0
})
@ -43,38 +54,101 @@ function App() {
<div key={i} className='hit-svg' style={{'--x': obj.x, '--y': obj.y} as CSSProperties}>
<FontAwesomeIcon icon={faBurst} />
</div>);
const corner = (x: number, y: number, count: number) => {switch (true) {
case x === 0 && y === 0:
return 'left-top-corner';
case x === count+1 && y === 0:
return 'right-top-corner';
case x === 0 && y === count+1:
return 'left-bottom-corner';
case x === count+1 && y === count+1:
return 'right-bottom-corner';
default:
return '';
}};
const border = (x: number, y: number, count: number) => {switch (true) {
case x === 0:
return 'left';
case y === 0:
return 'top';
case x === count+1:
return 'right';
case y === count+1:
return 'bottom';
default:
return '';
}};
for (let y = 0; y < count+2; y++) {
for (let x = 0; x < count+2; x++) {
const corner = [
x === 0 && y === 0 ? 'left-top-corner' : '',
x === count+1 && y === 0 ? 'right-top-corner' : '',
x === 0 && y === count+1 ? 'left-bottom-corner' : '',
x === count+1 && y === count+1 ? 'right-bottom-corner' : ''
].filter(s => s);
const border = [
x === 0 ? 'left' : '',
y === 0 ? 'top' : '',
x === count+1 ? 'right' : '',
y === count+1 ? 'bottom' : ''
].filter(s => s);
const borderType = corner.length ? corner[0] : border[0];
const action = x > 0 && x < count+1 && y > 0 && y < count+1;
const classNames = [
'border-tile',
borderType ? `edge ${borderType}` : '',
action ? 'action' : ''
].join(' ')
const cornerReslt = corner(x, y, count);
const borderType = cornerReslt ? cornerReslt : border(x, y, count);
const isGameTile = x > 0 && x < count+1 && y > 0 && y < count+1;
const classNames = ['border-tile'];
if (borderType)
classNames.push('edge', borderType);
if (isGameTile)
classNames.push('game-tile');
borderTiles.push(
<div
key={y*(count+2)+x}
className={classNames}
className={classNames.join(' ')}
style={{'--x': (x + 1), '--y': (y + 1)} as CSSProperties}
onClick={action ? () => setTarget({ event: true, x, y }) : () => {}}
onClick={() => {
if (isGameTile)
setTarget({ show: true, x, y });
}}
onMouseEnter={() => setTargetPreview(e => ({...e, newX: x, newY: y, shouldShow: isGameTile}))}
></div>
)
}
}
// handle visibility and position change of targetPreview
useEffect(() => {
const {newX, newY, shouldShow, appearOK, eventReady, show, x, y} = targetPreview;
const positionChange = !(x === newX && y === newY);
// if not ready or no new position
if (!eventReady || !positionChange)
return;
if (show) {
// hide preview to change position when hidden
setTargetPreview(e => ({...e, appearOK: false, eventReady: false, show: false}));
} else if (shouldShow && appearOK) {
// BUT only appear again if it's supposed to (in case the mouse left over the edge) and ()
setTargetPreview(e => ({...e, appearOK: false, eventReady: false, show: true, x: newX, y: newY}));
}
}, [targetPreview])
// enable targetPreview event again after 200 mil. sec.
useEffect(() => {
if (targetPreview.eventReady)
return;
const autoTimeout = setTimeout(() => {
setTargetPreview(e => ({...e, eventReady: true}));
}, 200);
// or abort if state has changed early
return () => {
clearTimeout(autoTimeout);
}
}, [targetPreview.eventReady]);
// approve targetPreview new position after 200 mil. sec.
useEffect(() => {
// early return to start cooldown only when about to show up
if (!targetPreview.shouldShow)
return;
const autoTimeout = setTimeout(() => {
setTargetPreview(e => ({...e, appearOK: true}));
}, 350);
// or abort if movement is repeated early
return () => {
clearTimeout(autoTimeout);
}
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
for (let i = 1; i <= 6; i++) {
shipElems.push(
@ -103,10 +177,10 @@ function App() {
{/* <img className='fog-left' src={`/fog/fog2.png`} alt={`fog1.png`} />
<img className='fog-right' src={`/fog/fog2.png`} alt={`fog1.png`} />
<img className='fog-middle' src={`/fog/fog4.png`} alt={`fog4.png`} /> */}
<div className={`hit-svg target ${target.event ? 'show' : ''}`} style={{'--x': target.x+1, '--y': target.y+1} as CSSProperties}>
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{'--x': target.x+1, '--y': target.y+1} as CSSProperties}>
<FontAwesomeIcon icon={faCrosshairs} />
</div>
<div className={`hit-svg target-preview`}>
<div className={`hit-svg target-preview ${targetPreview.show && (target.x !== targetPreview.x || target.y !== targetPreview.y) ? 'show' : ''}`} style={{'--x': targetPreview.x+1, '--y': targetPreview.y+1} as CSSProperties}>
<FontAwesomeIcon icon={faCrosshairs} />
</div>
</div>

View file

@ -0,0 +1,10 @@
export interface TargetPreviewType {
newX: number,
newY: number,
shouldShow: boolean,
appearOK: boolean,
eventReady: boolean,
show: boolean,
x: number,
y: number
}

View file

@ -36,7 +36,7 @@
#game-frame {
// border: 1px solid orange;
position: relative;
// position: relative;
$height: 945px;
$width: $height;
height: $height;
@ -63,73 +63,7 @@
&.edge {
border: 1px solid gray;
}
&.left-top-corner {
-webkit-mask-image: -webkit-gradient(linear, right bottom,
left top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.right-top-corner {
-webkit-mask-image: -webkit-gradient(linear, left bottom,
right top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.left-bottom-corner {
-webkit-mask-image: -webkit-gradient(linear, right top,
left bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.right-bottom-corner {
-webkit-mask-image: -webkit-gradient(linear, left top,
right bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.left {
-webkit-mask-image: -webkit-gradient(linear, right top,
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.right {
-webkit-mask-image: -webkit-gradient(linear, left top,
right top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.top {
-webkit-mask-image: -webkit-gradient(linear, left bottom,
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.bottom {
-webkit-mask-image: -webkit-gradient(linear, left top,
left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
$point-size: 1;
$count: 12;
$grid-elem-width: $count + 2;
$warst: $count + 1.5;
$one: calc($height / $warst);
$elements-in-grid: $grid-elem-width * $grid-elem-width;
// Inspired by https://stackoverflow.com/questions/35990505/get-position-of-mouse-pointer-in-css-without-javascript
@for $i from 1 through $elements-in-grid {
&:nth-child(#{$i}) {
&:hover {
&:not(.edge) ~ .target-preview {
opacity: 1;
}
& ~ .target-preview {
$row: ceil(calc($i / $grid-elem-width));
top: (
($row - 2)
* $one
+ ($one * 0.75)
);
left: (
($i
- (($row - 1) * $grid-elem-width)
- 2)
* $one
+ ($one * 0.75)
);
}
}
}
}
@include gradient-edge;
}
> :not(.border) {
box-sizing: border-box;
@ -212,24 +146,15 @@
&.target {
color: red;
opacity: 0;
&.show {
opacity: 1;
}
}
&.target-preview {
color: orange;
opacity: 0;
position: absolute;
@include transition(.2s);
$count: 12;
height: calc($height / ($count + 1.5));
width: calc($height / ($count + 1.5));
&.show {
opacity: 1;
}
}
&.show {
opacity: 1;
}
}
.r2 {

View file

@ -15,4 +15,39 @@
To {
opacity: 1;
}
}
@mixin gradient-edge {
&.left-top-corner {
-webkit-mask-image: -webkit-gradient(linear, right bottom,
left top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.right-top-corner {
-webkit-mask-image: -webkit-gradient(linear, left bottom,
right top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.left-bottom-corner {
-webkit-mask-image: -webkit-gradient(linear, right top,
left bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.right-bottom-corner {
-webkit-mask-image: -webkit-gradient(linear, left top,
right bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
}
&.left {
-webkit-mask-image: -webkit-gradient(linear, right top,
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.right {
-webkit-mask-image: -webkit-gradient(linear, left top,
right top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.top {
-webkit-mask-image: -webkit-gradient(linear, left bottom,
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
&.bottom {
-webkit-mask-image: -webkit-gradient(linear, left top,
left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
}
}