Skip to content

Commit

Permalink
📖 Add JSDoc documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianwachter committed Dec 11, 2023
1 parent 0f57845 commit 959a783
Showing 1 changed file with 104 additions and 0 deletions.
104 changes: 104 additions & 0 deletions src/scratch/scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,57 @@ export interface Vector {
y: number
}

/**
* A custom Scratch Card component extending HTMLElement.
* @class Scratch
* @extends {HTMLElement}
*/
export class Scratch extends HTMLElement {
/**
* Observed attributes for the custom element.
* @static
* @returns {Array<ScratchAttributes>} An array of observed attribute names.
*/
static get observedAttributes (): ScratchAttributes[] {
return [ScratchAttributes.BRUSH_SIZE,
ScratchAttributes.BRUSH_SHAPE,
ScratchAttributes.SCRATCH_COLOR,
ScratchAttributes.PERCENTAGE_UPDATE]
}

/**
* Local reference of the component's canvas element.
* @private {HTMLCanvasElement}
*/
canvas: HTMLCanvasElement

/**
* Local reference of the component's content that should be hidden by the canvas.
* @private {HTMLSlotElement}
*/
slotRef: HTMLSlotElement

/**
* The 2D context of the component's canvas. Can be null when it is not yet initialized.
* @private {CanvasRenderingContext2D|null}
*/
context: CanvasRenderingContext2D | null

/**
* A Vector defining the x and y coordinates of the last input - used for drawing a line between these values and the new input values.
* @private {Vector}
*/
lastPosition: Vector

/**
* Flag that determines if a drawing process is currently happening.
* @private {boolean}
*/
isDrawing: boolean = false

/**
* Creates an instance of Scratch and initializes the local references as well as registers the mouse and touch event handlers.
*/
constructor () {
super()

Expand All @@ -61,6 +94,10 @@ export class Scratch extends HTMLElement {
window.addEventListener('touchend', () => this.handleEnd(), false)
}

/**
* Lifecycle callback that gets invoked when the element is appended to the DOM. The slot shoudl be populated at this point.
* Uses the slot's width and height vlaues to set the canvas' size to hide the slot. Also fills the canvas initially with the defined color.
*/
connectedCallback (): void {
const slotChildNodeBoundingClientRect = this.slotRef.assignedElements()[0].getBoundingClientRect()
const newWidth = Math.floor(slotChildNodeBoundingClientRect.width)
Expand All @@ -73,57 +110,106 @@ export class Scratch extends HTMLElement {
this.fillArea()
}

/**
* Lifecycle callback that gets invoked when the attribute of the element changes.
* Reinitializes the canvas' context when ScratchAttributes.PERCENTAGE_UPDATE is toggled.
* @param {string} name - Name of the attribute that changed.
*/
attributeChangedCallback (name: string): void {
if (name === ScratchAttributes.PERCENTAGE_UPDATE) this.context = this.canvas.getContext('2d', { willReadFrequently: this.percentageUpdate })
}

/**
* Gets the value of the ScratchAttributes.BRUSH_SIZE attribute and parses it as a number.
* @returns {number}
*/
get brushSize (): number {
return parseInt(this.getAttribute(ScratchAttributes.BRUSH_SIZE) ?? '10', 10)
}

/**
* Gets the value of the ScratchAttributes.BRUSH_SHAPE attribute and parses it as a valid CanvasLineJoin.
* @returns {CanvasLineJoin}
*/
get brushShape (): CanvasLineJoin {
let shape = this.getAttribute(ScratchAttributes.BRUSH_SHAPE) ?? 'round'
if (shape !== 'bevel' && shape !== 'miter' && shape !== 'round') shape = 'round'
return shape as CanvasLineJoin
}

/**
* Gets the value of the ScratchAttributes.SCRATCH_COLOR attribute and parses it as a valid CSS color.
* @returns {string}
*/
get scratchColor (): string {
let color = this.getAttribute(ScratchAttributes.SCRATCH_COLOR) ?? '#000000'
if (!CSS.supports('color', color)) color = '#000000'
return color
}

/**
* Gets whether ScratchAttributes.PERCENTAGE_UPDATE is present on the element or not.
* @returns {boolean}
*/
get percentageUpdate (): boolean {
return this.hasAttribute(ScratchAttributes.PERCENTAGE_UPDATE)
}

/**
* Gets the offset of the canvas element from the top.
* @private
* @returns {number}
*/
get offsetTop (): number {
const { top } = this.canvas.getBoundingClientRect()
return top + document.body.scrollTop
}

/**
* Gets the offset of the canvas element from the left.
* @private
* @returns {number}
*/
get offsetLeft (): number {
const { left } = this.canvas.getBoundingClientRect()
return left + document.body.scrollLeft
}

/**
* Handles an event type agnostic input start event.
* @param {Vector} { x, y } The x and y coordinates where the start event is triggered.
* @returns {void}
*/
handleStart ({ x, y }: Vector): void {
this.lastPosition = { x: x - this.offsetLeft, y: y - this.offsetTop }
this.scratch({ x: x - this.offsetLeft, y: y - this.offsetTop })
this.isDrawing = true
if (this.percentageUpdate) this.calcAreaCleared()
}

/**
* Handles an event type agnostic input move event.
* @param {Vector} { x, y } The x and y coordinates where the move event is triggered.
* @returns {void}
*/
handleMove ({ x, y }: Vector): void {
if (!this.isDrawing) return
this.scratch({ x: x - this.offsetLeft, y: y - this.offsetTop })
if (this.percentageUpdate) this.calcAreaCleared()
}

/**
* Handles an event type agnostic input end or cancelled event.
* @returns {void}
*/
handleEnd (): void {
this.isDrawing = false
}

/**
* Fills the canvas with the defined color.
* @returns {void}
*/
fillArea (): void {
if (this.context === null) return
const { width, height } = this.context.canvas
Expand All @@ -133,6 +219,10 @@ export class Scratch extends HTMLElement {
if (this.percentageUpdate) this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, 0)
}

/**
* Clears the canvas.
* @returns {void}
*/
clearArea (): void {
if (this.context === null) return
const { width, height } = this.context.canvas
Expand All @@ -141,6 +231,11 @@ export class Scratch extends HTMLElement {
if (this.percentageUpdate) this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, 100)
}

/**
* Clears the color off the canvas at the given coordinates.
* @param {Vector} { x, y } The coordinates at which color should be cleared off.
* @returns {void}
*/
scratch ({ x, y }: Vector): void {
if (this.context === null) return
this.context.beginPath()
Expand All @@ -155,6 +250,10 @@ export class Scratch extends HTMLElement {
this.lastPosition = { x, y }
}

/**
* Calculates the current amount of cleared pixels with an inaccuracy of 150 from the canvas' ImageData.
* @returns {void}
*/
calcAreaCleared (): void {
if (this.context === null) return
const { width, height } = this.context.canvas
Expand All @@ -169,6 +268,11 @@ export class Scratch extends HTMLElement {
this.emitEvent(ScratchEvents.PERCENTAGE_UPDATE, percentage)
}

/**
* Emits given event with given payload.
* @param {ScratchEvents} eventType The name of the event that should be emitted.
* @param {number} payload The payload for the emitted event.
*/
emitEvent (eventType: ScratchEvents, payload: number): void {
const event = new CustomEvent(eventType, { detail: payload })
this.dispatchEvent(event)
Expand Down

0 comments on commit 959a783

Please sign in to comment.