Skip to content

Commit 5a187a7

Browse files
committed
smoother renders
1 parent 1ae935c commit 5a187a7

File tree

6 files changed

+202
-123
lines changed

6 files changed

+202
-123
lines changed

src/pages/lines/components/Range.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ const SegmentBar = styled.div`
1313
align-items: center;
1414
gap: ${SEGMENT_GAP}px;
1515
`
16-
const Segment = styled.span<{ color: string; active: boolean }>`
16+
const Segment = styled.span<{ color: string; checked: boolean }>`
1717
margin-top: ${MARGIN_BOTTOM};
1818
display: inline-block;
1919
border-radius: 0 4px;
2020
height: 26px;
2121
min-width: ${SEGMENT_WIDTH}px;
22-
background: ${({ color, active }) => (active ? `${lighten(0.2, color)}` : '#444')};
23-
box-shadow: ${({ active, color }) => (active ? `0 0 1px ${color}` : 'none')};
22+
background: ${({ color, checked }) => (checked ? `${lighten(0.2, color)}` : '#444')};
23+
box-shadow: ${({ checked, color }) => (checked ? `0 0 1px ${color}` : 'none')};
2424
2525
`
2626
const Input = styled.input`
@@ -84,7 +84,7 @@ const Range: React.FC<{
8484
<Wrapper>
8585
<SegmentBar>
8686
{SEGMENT_VALUES.map(v => (
87-
<Segment key={v} color={color} active={v <= value}/>
87+
<Segment key={v} color={color} checked={(v <= value)}/>
8888
))}
8989
</SegmentBar>
9090
<Input
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as React from 'react'
2+
3+
interface Props {
4+
backgroundSize: number,
5+
fill: string,
6+
rotation: number,
7+
rotationCenter: number
8+
}
9+
10+
const ShapeBase: React.FC<React.PropsWithChildren<Props>> = ({
11+
backgroundSize,
12+
rotation,
13+
rotationCenter,
14+
children
15+
}) => {
16+
return (
17+
<svg
18+
xmlns="http://www.w3.org/2000/svg"
19+
version="1.1"
20+
viewBox="0 0 400 400"
21+
height={backgroundSize}
22+
width={backgroundSize}
23+
>
24+
<g
25+
transform={`rotate(${rotation}, ${rotationCenter}, ${rotationCenter})`}
26+
>
27+
{children}
28+
</g>
29+
</svg>
30+
);
31+
}
32+
33+
const Diagonal: React.FC<Props> = (props) => {
34+
return (
35+
<ShapeBase {...props}>
36+
<path d="M12 5 H395 V388 Z" fill={props.fill} />
37+
<path d="M5 12 V395 H388 Z" fill={props.fill} />
38+
</ShapeBase>
39+
);
40+
}
41+
42+
const DoubleDiagonal: React.FC<Props> = (props) => {
43+
return (
44+
<ShapeBase {...props}>
45+
<path d="M12 5 H395 V188 Z" fill={props.fill} />
46+
<path d="M5 12 V195 H388 Z" fill={props.fill} />
47+
<path d="M12 205 H395 V388 Z" fill={props.fill} />
48+
<path d="M5 212 V395 H388 Z" fill={props.fill} />
49+
</ShapeBase>
50+
);
51+
}
52+
53+
const Wedge: React.FC<Props> = (props) => {
54+
return (
55+
<ShapeBase {...props}>
56+
<path d="M5 188 V5 H388 Z" fill={props.fill} />
57+
<path d="M8.5 200 L395 12 V388 Z" fill={props.fill} />
58+
<path d="M5 212 V395 H388 Z" fill={props.fill} />
59+
</ShapeBase>
60+
);
61+
}
62+
63+
const WedgeAndLine: React.FC<Props> = (props) => {
64+
return (
65+
<ShapeBase {...props}>
66+
<path d="M5 188 V5 H388 Z" fill={props.fill} />
67+
<path d="M12 195 H395 V12 Z" fill={props.fill} />
68+
<path d="M12 205 H395 V388 Z" fill={props.fill} />
69+
<path d="M5 212 V395 H388 Z" fill={props.fill} />
70+
</ShapeBase>
71+
);
72+
}
73+
74+
const TwoRectangles: React.FC<Props> = (props) => {
75+
return (
76+
<ShapeBase {...props}>
77+
<path d="M5 5 V395 h190 V5 Z" fill={props.fill} />
78+
<path d="M205 5 V395 h190 V5 Z" fill={props.fill} />
79+
</ShapeBase>
80+
);
81+
}
82+
83+
const ThreeRectangles: React.FC<Props> = (props) => {
84+
return (
85+
<ShapeBase {...props}>
86+
<path d="M5 5 V395 h123.3 V5 Z" fill={props.fill} />
87+
<path d="M138.3 5 V395 h123.3 V5 Z" fill={props.fill} />
88+
<path d="M271.6 5 V395 h123.3 V5 Z" fill={props.fill} />
89+
</ShapeBase>
90+
);
91+
}
92+
93+
const FourRectangles: React.FC<Props> = (props) => {
94+
return (
95+
<ShapeBase {...props}>
96+
<path d="M5 5 V395 h87.5 V5 Z" fill={props.fill} />
97+
<path d="M105 5 V395 h87.5 V5 Z" fill={props.fill} />
98+
<path d="M205 5 V395 h87.5 V5 Z" fill={props.fill} />
99+
<path d="M305 5 V395 h87.5 V5 Z" fill={props.fill} />
100+
</ShapeBase>
101+
);
102+
}
103+
104+
const Shapes = [
105+
Diagonal,
106+
DoubleDiagonal,
107+
Wedge,
108+
WedgeAndLine,
109+
TwoRectangles,
110+
ThreeRectangles,
111+
FourRectangles,
112+
]
113+
export default Shapes;

src/pages/lines/components/Tiler.tsx

Lines changed: 54 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import * as React from 'react'
22
import styled from 'styled-components'
3+
import Shapes from './Shapes'
4+
import { backgroundColor } from '..'
5+
import { seededRandom } from '../../../utils'
36

4-
const Container = styled.div`
7+
const Container = styled.div<{color: string}>`
58
height: 100vh;
69
width: 100vw;
710
overflow: hidden;
11+
background-color: ${({ color }) => color};
812
`
913
const Row = styled.div`
1014
white-space: nowrap;
@@ -13,77 +17,44 @@ const SvgWrapper = styled.div`
1317
& svg {
1418
margin-bottom: -4px;
1519
margin-top: 1px;
16-
1720
}
1821
`
1922

20-
function lines(
21-
backgroundSize: number,
22-
fill: string,
23-
rotation: number,
24-
rotationCenter: number
25-
): string[] {
26-
return [
27-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}">
28-
<g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})">
29-
<path d="M12 5 H395 V388 Z" fill="${fill}"/>
30-
<path d="M5 12 V395 H388 Z" fill="${fill}"/>
31-
</g>
32-
</svg>`,
33-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}">
34-
<g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})">
35-
<path d="M5 188 V5 H388 Z" fill="${fill}"/>
36-
<path d="M8.5 200 L395 12 V388 Z" fill="${fill}"/>
37-
<path d="M5 212 V395 H388 Z" fill="${fill}"/>
38-
</g></svg>`,
39-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}"><g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})"> <path d="M5 188 V5 H388 Z" fill="${fill}"/>
40-
<path d="M12 195 H395 V12 Z" fill="${fill}"/>
41-
<path d="M12 205 H395 V388 Z" fill="${fill}"/>
42-
<path d="M5 212 V395 H388 Z" fill="${fill}"/>
43-
</g></svg>`,
44-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}"><g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})"> <path d="M12 5 H395 V188 Z" fill="${fill}"/>
45-
<path d="M5 12 V195 H388 Z" fill="${fill}"/>
46-
<path d="M12 205 H395 V388 Z" fill="${fill}"/>
47-
<path d="M5 212 V395 H388 Z" fill="${fill}"/>
48-
</g></svg>`,
49-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}"><g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})"> <path d="M5 5 V395 h90 V5 Z" fill="${fill}"/>
50-
<path d="M105 5 V395 h90 V5 Z" fill="${fill}"/>
51-
<path d="M205 5 V395 h90 V5 Z" fill="${fill}"/>
52-
<path d="M305 5 V395 h90 V5 Z" fill="${fill}"/>
53-
</g></svg>`,
54-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}"><g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})"> <path d="M5 5 V395 h123.3 V5 Z" fill="${fill}"/>
55-
<path d="M138.3 5 V395 h123.3 V5 Z" fill="${fill}"/>
56-
<path d="M271.6 5 V395 h123.3 V5 Z" fill="${fill}"/>
57-
</g></svg>`,
58-
`<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 400 400" height="${backgroundSize}" width="${backgroundSize}"><g transform="rotate(${rotation}, ${rotationCenter}, ${rotationCenter})"> <path d="M5 5 V395 h190 V5 Z" fill="${fill}"/>
59-
<path d="M205 5 V395 h190 V5 Z" fill="${fill}"/>
60-
</g></svg>`,
61-
]
62-
}
63-
6423
interface Props {
6524
rotationCenter: number
6625
rotationGranularity: number
6726
color: string
6827
}
6928

29+
interface Indices {
30+
shape: number
31+
rotation: number
32+
}
33+
7034
const TILE_SIZE = 70
7135

72-
function getGridSize(width: number, height: number) {
36+
const getGridSize = (width: number, height: number) => {
7337
return {
7438
rows: Math.ceil(height / TILE_SIZE),
7539
cols: Math.ceil(width / TILE_SIZE),
7640
}
7741
}
7842

79-
function seededRandom(seed: number) {
80-
// Mulberry32 PRNG
81-
return function () {
82-
let t = (seed += 0x6d2b79f5)
83-
t = Math.imul(t ^ (t >>> 15), t | 1)
84-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
85-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
86-
}
43+
const getIndices = (rows: number, cols: number, rotationGranularity: number) => {
44+
const indices: Indices[][] = []
45+
for (let i = 0; i < rows; i++) {
46+
indices[i] = []
47+
const rand = seededRandom(41 * i + 1)
48+
for (let j = 0; j < cols; j++) {
49+
indices[i][j] = {
50+
shape: Math.floor(rand() * Shapes.length),
51+
rotation:
52+
Math.floor(rand() * rotationGranularity) *
53+
(360 / rotationGranularity),
54+
}
55+
}
56+
}
57+
return indices;
8758
}
8859

8960
const Tiler: React.FC<Props> = ({
@@ -92,12 +63,10 @@ const Tiler: React.FC<Props> = ({
9263
color,
9364
}) => {
9465
const [dimensions, setDimensions] = React.useState({
95-
width: 100,
96-
height: 100,
66+
width: 1000,
67+
height: 1000,
9768
})
98-
99-
// Store the shape/rotation indices for each tile
100-
const [tileIndices, setTileIndices] = React.useState<number[][] | null>(null)
69+
const [tileIndices, setTileIndices] = React.useState<Indices[][] | null>(null)
10170

10271
React.useEffect(() => {
10372
function updateDimensions() {
@@ -112,52 +81,36 @@ const Tiler: React.FC<Props> = ({
11281
}, [])
11382

11483
React.useEffect(() => {
115-
// Precompute indices for shapes and rotations
11684
const { rows, cols } = getGridSize(dimensions.width, dimensions.height)
117-
// Use a seed based on dimensions to keep it stable between renders
118-
const seed = dimensions.width * 100000 + dimensions.height
119-
const rand = seededRandom(seed)
120-
const shapeIndices: number[][] = []
121-
for (let i = 0; i < rows; i++) {
122-
shapeIndices[i] = []
123-
for (let j = 0; j < cols; j++) {
124-
shapeIndices[i][j] = Math.floor(rand() * lines(0, '', 0, 0).length)
125-
}
126-
}
127-
setTileIndices(shapeIndices)
128-
}, [dimensions.width, dimensions.height])
129-
130-
const { rows, cols } = getGridSize(dimensions.width, dimensions.height)
85+
setTileIndices(getIndices(rows, cols, rotationGranularity))
86+
}, [dimensions.width, dimensions.height, rotationGranularity])
13187

132-
// Ensure tileIndices and rotationIndices are initialized and match the current grid size
133-
if (
134-
!tileIndices ||
135-
tileIndices.length !== rows ||
136-
tileIndices.some(rowArr => rowArr.length !== cols)
137-
) {
138-
return null
139-
}
140-
141-
const lineArray: JSX.Element[] = []
142-
for (let i = 0; i < rows; i++) {
143-
let row = ''
144-
for (let j = 0; j < cols; j++) {
145-
const rotation = Math.floor(Math.random()*rotationGranularity) * (360/rotationGranularity)
146-
const shapes = lines(TILE_SIZE, '#1a1a2e', rotation, rotationCenter)
147-
row += shapes[tileIndices[i][j]]
148-
}
149-
lineArray.push(
150-
<Row
151-
key={i}
152-
style={{ backgroundColor: color }}
153-
dangerouslySetInnerHTML={{ __html: row }}
154-
/>
155-
)
88+
if (!tileIndices) {
89+
return <Container color={backgroundColor} />
15690
}
15791

15892
return (
159-
<Container>
160-
<SvgWrapper>{lineArray}</SvgWrapper>
93+
<Container color={color}>
94+
<SvgWrapper>
95+
{tileIndices.map((row, i) => {
96+
return (
97+
<Row key={i}>
98+
{row.map((tile, j) => {
99+
const Shape = Shapes[tile.shape]
100+
return (
101+
<Shape
102+
key={`${i}-${j}`}
103+
backgroundSize={TILE_SIZE}
104+
fill={backgroundColor}
105+
rotation={tile.rotation}
106+
rotationCenter={rotationCenter}
107+
/>
108+
)
109+
})}
110+
</Row>
111+
)
112+
})}
113+
</SvgWrapper>
161114
</Container>
162115
)
163116
}

src/pages/lines/components/Toggle.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ type Props = {
1212

1313
const Label = styled.label`
1414
display: grid;
15-
margin-right: 1rem;
1615
cursor: pointer;
1716
`
1817
const Switch = styled.span<{ color: string; checked: boolean }>`
@@ -23,7 +22,6 @@ const Switch = styled.span<{ color: string; checked: boolean }>`
2322
background: transparent;
2423
border: 2px solid ${({ checked, color }) => (checked ? lighten(0.1, color) : '#444')};
2524
box-shadow: ${({ checked, color }) => (checked ? `0 0 1px ${color}` : 'none')};
26-
// border-radius: 22px;
2725
transition: background 0.2s;
2826
vertical-align: middle;
2927
`
@@ -34,7 +32,6 @@ const Slider = styled.span<{ color: string; checked: boolean }>`
3432
width: 18px;
3533
height: 18px;
3634
background: ${({ checked, color }) => (checked ? lighten(0.1, color) : '#333')};
37-
// border-radius: 50%;
3835
transition: left 0.2s;
3936
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
4037
`

0 commit comments

Comments
 (0)