Draw Together
Draw Together is a little collaborative drawing toy I made with Kitten on Saturday night.
You can play with it in the live embed above, watch the video in this post to learn how to build it, view its source code, and read through a complete break-down of the source code, below.
Update to video: Add the following code to the
button
elements to make them accessible by allowing the buttons to be differentiated for people using assistive technologies:aria-label='pixel-${rowIndex}-${columnIndex}'
Source code
The code sets up a real-time collaborative drawing tool on a 20×20 pixel grid where people can click to toggle the colour of each pixel.
Here’s a breakdown of the entire source code, detailing what each section does:
Create 20×20 pixel grid
const rows = new Array(20).fill().map(row => new Array(20).fill(0))
const colours = ['white', 'black']
Creates a 20x20 grid initialised with zeros. Each cell will store an index corresponding to the colours
array.
Validation function
const isValid = data => typeof data === 'object' && typeof data.rowIndex === 'number' && typeof data.columnIndex === 'number' && data.rowIndex >= 0 && data.rowIndex <= rows.length-1 && data.columnIndex >= 0 && data.columnIndex <= rows[0].length-1
Checks if the data object has valid rowIndex
and columnIndex
within grid bounds.
Handle connections and pixel updates
export function onConnect ({ page }) {
page.on('pixel', data => {
if (!isValid(data)) return
rows[data.rowIndex][data.columnIndex]++
if (rows[data.rowIndex][data.columnIndex] === colours.length) {
rows[data.rowIndex][data.columnIndex] = 0
}
page.everyone.send(kitten.html`
<${Pixel} rowIndex=${data.rowIndex} columnIndex=${data.columnIndex} pixelColourIndex=${rows[data.rowIndex][data.columnIndex]} />
`)
})
}
Listens for pixel updates, validates data, updates the grid, and sends the updated pixel state to everyone connected.
Pixel component
const Pixel = ({ rowIndex, columnIndex, pixelColourIndex }) => kitten.html`
<button
id='pixel-${rowIndex}-${columnIndex}'
aria-label='pixel-${rowIndex}-${columnIndex}'
name='pixel'
style='
background-color: ${colours[pixelColourIndex]};
top: calc(5% * ${rowIndex});
left: calc(5% * ${columnIndex});
'
connect
data='{rowIndex: ${rowIndex}, columnIndex: ${columnIndex}}'
morph
></button>
`
Defines a button element representing a pixel, styled according to its colour and position.
Canvas component
const Canvas = () => kitten.html`${
rows.map((row, rowIndex) => row.map((pixelColourIndex, columnIndex) => kitten.html`
<${Pixel} rowIndex=${rowIndex} columnIndex=${columnIndex} pixelColourIndex=${pixelColourIndex} />
`))
}`
Renders the entire grid by mapping over each row and column, creating Pixel
components.
Styles component
const Styles = () => kitten.css`
[name='pixel'] {
position: fixed;
width: 5%;
height: 5%;
border: 0;
}
`
Applies CSS styles to the pixel buttons to size and position them correctly.
Main export
export default () => kitten.html`
<page title='Draw Together'>
<${Canvas} />
<${Styles} />
`
Constructs the main page with the canvas and styles.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.