diff --git a/README.md b/README.md index 887c67c..976d027 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## Nebula -### Nebula is a lightweight (1kb compressed) JavaScript library for creating beautifull universe animations with React / Next / Gatsby. +### Nebula is a lightweight (1kb compressed) JavaScript library that creates beautiful universe animations with React / Next / Gatsby. Including configurable Stars, Nebulas, Comets, Planets and Suns. Compatible with SSR @@ -43,12 +43,12 @@ The canvas is positioned ``absolute`` and takes the size of its parent. ### `Config` key | option type | default | Comment ---|-----------|---|--- -`starsCount` | `number` | `350` | Recommended to keep smaller than `1000` +`starsCount` | `number` | `350` | Recommended: < `1000` `starsColor` | `string` | `#ffffff` `starsRotationSpeed` | `number` | `3` -`cometFrequence` | `number` | `2` | Value `0` disables the comets +`cometFrequence` | `number` | `10` | `0` disables the comets `nebulasIntensity` | `number` | `10` `nebulasColors` | `string[]` accept rgb and hex | `["rgb(5,63,157)", "rgb(42,112,25)", "rgb(182,41,44)"]` -`solarSystemScale` | `number` | `1` | Value `0` hides the solar system -`solarSystemDistance` | `number` | `65` | Values greater than `100` can be out of screen +`solarSystemScale` | `number` | `1` | `0` hides the solar system +`solarSystemDistance` | `number` | `65` | Recommended: < `100` `solarSystemRotationSpeed` | `number` | `100` diff --git a/package.json b/package.json index cf928c4..2ad4b44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flodlc/nebula", - "version": "1.0.27", + "version": "1.0.30", "main": "dist/index.cjs.js", "module": "dist/index.es.js", "typings": "dist/index.d.ts", diff --git a/src/DEFAULT_CONFIG.ts b/src/DEFAULT_CONFIG.ts index f4b38a8..3c1df3e 100644 --- a/src/DEFAULT_CONFIG.ts +++ b/src/DEFAULT_CONFIG.ts @@ -2,7 +2,7 @@ export const DEFAULT_CONFIG = { starsCount: 350, starsColor: "#FFFFFF", starsRotationSpeed: 3, - cometFrequence: 2, + cometFrequence: 10, nebulasIntensity: 10, nebulasColors: ["rgb(27,2,140)", "rgb(22,91,2)", "#880554"], solarSystemScale: 1, diff --git a/src/View/draw.ts b/src/View/draw.ts index e03e590..4242db0 100644 --- a/src/View/draw.ts +++ b/src/View/draw.ts @@ -1,4 +1,5 @@ import { Astre } from "src/astres/Astre"; +import { Drawable } from "src/astres/types"; export const drawAstres = ({ astres, @@ -8,7 +9,7 @@ export const drawAstres = ({ clear = true, fps = 60, }: { - astres: Record; + astres: Record; canvas: HTMLCanvasElement; bgColor?: string; play: boolean; diff --git a/src/View/useAstres.ts b/src/View/useAstres.ts index 5f2338d..bd8c3ff 100644 --- a/src/View/useAstres.ts +++ b/src/View/useAstres.ts @@ -1,4 +1,4 @@ -import { RefObject, useLayoutEffect } from "react"; +import { RefObject, useLayoutEffect, useRef } from "react"; import { SystemConfig } from "src/types"; import { generateStars } from "src/utils/generateStars"; import { Star } from "src/astres/Star"; @@ -7,6 +7,9 @@ import { Planet } from "src/astres/Planet"; import { Sun } from "src/astres/Sun"; import { drawAstres } from "src/View/draw"; import { generateSolarSytem } from "src/utils/generateSolarSytem"; +import { Comet } from "src/astres/Comet"; +import { FPS } from "src/config"; +import { Drawable } from "src/astres/types"; export const useAstres = ({ canvasSize, @@ -17,63 +20,76 @@ export const useAstres = ({ canvasRef: RefObject; config: Required; }) => { + const astres = useRef>(); useLayoutEffect(() => { if (!canvasSize.width) return () => undefined; - const ctx = (canvasRef.current as HTMLCanvasElement).getContext("2d"); - const stars = generateStars({ - rotationSpeed: config.starsRotationSpeed, - count: config.starsCount, - color: config.starsColor, - }).reduce( - (stars, starConfig) => ({ - ...stars, - [starConfig.name]: new Star({ - ...(starConfig as any), - ctx, - }), - }), - {} as Record - ); + const canvas = canvasRef.current as HTMLCanvasElement; + const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; - const comets = generateComet({ - frequence: config.cometFrequence, - }).reduce( - (comets, cometConfig) => ({ - ...comets, - [cometConfig.name]: new Star({ - ctx, - ...(cometConfig as any), - origin: cometConfig.origin ? comets[cometConfig.origin] : undefined, - }), - }), - {} as Record - ); + if (!astres.current) { + astres.current = createAstres({ config, ctx }); + } - const planets = generateSolarSytem({ - scale: config.solarSystemScale, - distance: config.solarSystemDistance, - rotationSpeed: config.solarSystemRotationSpeed, - }).reduce((planets, planetConfig) => { - const Type = { - planet: Planet, - sun: Sun, - }[planetConfig.type as "sun" | "planet"]; - return { - ...planets, - [planetConfig.name]: new Type({ - ctx, - ...(planetConfig as any), - origin: planetConfig.origin - ? planets[planetConfig.origin] - : undefined, - }), - }; - }, {} as Record); return drawAstres({ - astres: { ...stars, ...comets, ...planets }, + astres: astres.current, canvas: canvasRef.current as HTMLCanvasElement, play: true, - fps: 40, + fps: FPS, }); }, [canvasSize.width, canvasSize.height, config]); }; + +const createAstres = ({ + config, + ctx, +}: { + config: Required; + ctx: CanvasRenderingContext2D; +}) => { + const stars = generateStars({ + rotationSpeed: config.starsRotationSpeed, + count: config.starsCount, + color: config.starsColor, + }).reduce( + (stars, starConfig) => ({ + ...stars, + [starConfig.name]: new Star({ + ...(starConfig as any), + ctx, + }), + }), + {} as Record + ); + + const planets = generateSolarSytem({ + scale: config.solarSystemScale, + distance: config.solarSystemDistance, + rotationSpeed: config.solarSystemRotationSpeed, + }).reduce((planets, planetConfig) => { + const Type = { + planet: Planet, + sun: Sun, + }[planetConfig.type as "sun" | "planet"]; + return { + ...planets, + [planetConfig.name]: new Type({ + ctx, + ...(planetConfig as any), + origin: planetConfig.origin ? planets[planetConfig.origin] : undefined, + }), + }; + }, {} as Record); + + const comets = generateComet({ frequence: config.cometFrequence }).reduce( + (stars, cometConfig) => ({ + ...stars, + [cometConfig.name]: new Comet({ + ...cometConfig, + ctx, + }), + }), + {} as Record + ); + + return { ...stars, ...comets, ...planets }; +}; diff --git a/src/View/useNebulas.ts b/src/View/useNebulas.ts index 68c9bf0..b85488f 100644 --- a/src/View/useNebulas.ts +++ b/src/View/useNebulas.ts @@ -1,8 +1,9 @@ -import { RefObject, useLayoutEffect } from "react"; +import { RefObject, useLayoutEffect, useRef } from "react"; import { generateNebulas } from "src/utils/generateNebulas"; import { Nebula } from "src/astres/Nebula"; import { drawAstres } from "src/View/draw"; import { SystemConfig } from "src/types"; +import { Drawable } from "src/astres/types"; export const useNebulas = ({ canvasSize, @@ -13,28 +14,43 @@ export const useNebulas = ({ canvasRef: RefObject; config: Required; }) => { + const nebulas = useRef>(); useLayoutEffect(() => { if (!canvasSize.width) return () => undefined; - const ctx = (canvasRef.current as HTMLCanvasElement).getContext("2d"); - const nebulas = generateNebulas({ - intensity: config.nebulasIntensity, - colors: config.nebulasColors, - }).reduce( - (comets, nebulaConfig) => ({ - ...comets, - [nebulaConfig.name]: new Nebula({ - ...(nebulaConfig as any), - ctx, - }), - }), - {} as Record - ); + const canvas = canvasRef.current as HTMLCanvasElement; + const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + + if (!nebulas.current) { + nebulas.current = createNebulas({ ctx, config }); + } return drawAstres({ - astres: nebulas, + astres: nebulas.current, canvas: canvasRef.current as HTMLCanvasElement, play: false, bgColor: "rgb(8, 8, 8)", }); }, [canvasSize.width, canvasSize.height, config]); }; + +const createNebulas = ({ + config, + ctx, +}: { + config: Required; + ctx: CanvasRenderingContext2D; +}) => { + return generateNebulas({ + intensity: config.nebulasIntensity, + colors: config.nebulasColors, + }).reduce( + (comets, nebulaConfig) => ({ + ...comets, + [nebulaConfig.name]: new Nebula({ + ...(nebulaConfig as any), + ctx, + }), + }), + {} as Record + ); +}; diff --git a/src/astres/Astre.ts b/src/astres/Astre.ts index 558158b..fd6c80a 100644 --- a/src/astres/Astre.ts +++ b/src/astres/Astre.ts @@ -6,10 +6,10 @@ export abstract class Astre implements Drawable { rgb: [number, number, number]; rotateSpeed: number; angle: number; - origin?: Drawable; + origin?: Astre; relativeDistance: number; - constructor({ + protected constructor({ ctx, width, rotateSpeed, @@ -23,7 +23,7 @@ export abstract class Astre implements Drawable { rotateSpeed: number; distance: number; rgb: [number, number, number]; - origin?: Drawable; + origin?: Astre; invisible?: boolean; startAngle?: number; }) { diff --git a/src/astres/Comet.ts b/src/astres/Comet.ts new file mode 100644 index 0000000..7086f2b --- /dev/null +++ b/src/astres/Comet.ts @@ -0,0 +1,113 @@ +import { Drawable } from "src/astres/types"; +import { FPS } from "src/config"; + +const SPEED = 150; + +export class Comet implements Drawable { + ctx: CanvasRenderingContext2D; + frequence: number; + + constructor({ + ctx, + frequence, + }: { + ctx: CanvasRenderingContext2D; + frequence: number; + }) { + this.ctx = ctx; + this.frequence = frequence; + } + + getOriginCoords() { + return [1, 1] as [number, number]; + } + getAngle() { + return 1; + } + getWidth() { + return 1; + } + + private getCanvasWidth() { + return this.ctx.canvas.width; + } + + private getCanvasHeight() { + return this.ctx.canvas.height; + } + + private showConfig: + | { + speed: number; + startCoords: { x: number; y: number }; + direction: number; + distanceToTarget: number; + color: string; + width: number; + startOpacity: number; + } + | undefined; + + private speed = SPEED; + private x = 0; + private y = 0; + private opacity = 0; + private co = 0; + private coa = 0; + private move() { + if (this.showConfig) { + this.x += this.speed * Math.cos(this.showConfig.direction); + this.y += this.speed * Math.sin(this.showConfig.direction); + const { x: startX, y: startY } = this.showConfig.startCoords; + const distance = Math.sqrt( + Math.pow(this.x - startX, 2) + Math.pow(this.y - startY, 2) + ); + const showAvancement = distance / this.showConfig.distanceToTarget; + this.opacity = Math.min(this.showConfig.startOpacity + showAvancement, 1); + + if (distance > this.showConfig.distanceToTarget) { + this.showConfig = undefined; + } + return; + } + + const shouldCreateANewShow = Math.random() > 1 - this.frequence / 100 / FPS; + this.co++; + if (shouldCreateANewShow) { + this.coa++; + const fromAngle = Math.random() * 2 * Math.PI; + const maxSideSize = Math.max( + this.getCanvasHeight(), + this.getCanvasWidth() + ); + this.showConfig = { + startCoords: { + x: + (Math.cos(fromAngle) * maxSideSize) / 2 + this.getCanvasWidth() / 2, + y: + (Math.sin(fromAngle) * maxSideSize) / 2 + + this.getCanvasHeight() / 2, + }, + direction: fromAngle + Math.PI + (Math.random() * Math.PI) / 6, + distanceToTarget: maxSideSize, + speed: Math.random() * SPEED + SPEED, + color: "width", + width: 1 + Math.random() * 3, + startOpacity: Math.random() * 0.5, + }; + this.x = this.showConfig.startCoords.x; + this.y = this.showConfig.startCoords.y; + } + } + + draw() { + this.move(); + if (!this.showConfig) return; + this.ctx.save(); + this.ctx.arc(this.x, this.y, this.showConfig.width, 0, Math.PI * 2); + this.ctx.fillStyle = this.showConfig.color; + this.ctx.globalAlpha = this.opacity; + this.ctx.fill(); + this.ctx.restore(); + } +} diff --git a/src/astres/Nebula.ts b/src/astres/Nebula.ts index 1873775..99e4c0e 100644 --- a/src/astres/Nebula.ts +++ b/src/astres/Nebula.ts @@ -1,5 +1,4 @@ import { Astre } from "src/astres/Astre"; -import { Drawable } from "src/astres/types"; import { roundCoords } from "src/utils/roundCoords"; export class Nebula extends Astre { @@ -20,7 +19,7 @@ export class Nebula extends Astre { rotateSpeed: number; distance: number; rgb: [number, number, number]; - origin?: Drawable; + origin?: Astre; invisible?: boolean; startAngle?: number; intensity: number; diff --git a/src/astres/Planet.ts b/src/astres/Planet.ts index 46da3b6..e7e622a 100644 --- a/src/astres/Planet.ts +++ b/src/astres/Planet.ts @@ -1,5 +1,4 @@ import { Astre } from "src/astres/Astre"; -import { Drawable } from "src/astres/types"; import { roundCoords } from "src/utils/roundCoords"; export class Planet extends Astre { @@ -17,7 +16,7 @@ export class Planet extends Astre { rotateSpeed: number; distance: number; rgb: [number, number, number]; - origin?: Drawable; + origin?: Astre; startAngle?: number; }) { super({ diff --git a/src/astres/Star.ts b/src/astres/Star.ts index 3b01f24..f632aab 100644 --- a/src/astres/Star.ts +++ b/src/astres/Star.ts @@ -1,5 +1,4 @@ import { Astre } from "src/astres/Astre"; -import { Drawable } from "src/astres/types"; import { roundCoords } from "src/utils/roundCoords"; export class Star extends Astre { @@ -16,7 +15,7 @@ export class Star extends Astre { width: number; rotateSpeed: number; distance: number; - origin?: Drawable; + origin?: Astre; invisible?: boolean; startAngle?: number; rgb: [number, number, number]; diff --git a/src/astres/Sun.ts b/src/astres/Sun.ts index 7c3068f..04d103f 100644 --- a/src/astres/Sun.ts +++ b/src/astres/Sun.ts @@ -1,5 +1,4 @@ import { Astre } from "src/astres/Astre"; -import { Drawable } from "src/astres/types"; import { roundCoords } from "src/utils/roundCoords"; export class Sun extends Astre { @@ -17,7 +16,7 @@ export class Sun extends Astre { rotateSpeed: number; distance: number; rgb: [number, number, number]; - origin?: Drawable; + origin?: Astre; startAngle?: number; }) { super({ diff --git a/src/astres/types.ts b/src/astres/types.ts index c66d366..1bd62df 100644 --- a/src/astres/types.ts +++ b/src/astres/types.ts @@ -1,6 +1,3 @@ export interface Drawable { draw: () => void; - getOriginCoords: () => [number, number]; - getAngle: () => number; - getWidth: () => number; } diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..fbe9452 --- /dev/null +++ b/src/config.ts @@ -0,0 +1 @@ +export const FPS = 40; diff --git a/src/templates/bigSolarSystem.ts b/src/templates/bigSolarSystem.ts deleted file mode 100644 index 9e28dd8..0000000 --- a/src/templates/bigSolarSystem.ts +++ /dev/null @@ -1,83 +0,0 @@ -export const bigSolarSystem = [ - { - name: "Sun", - type: "sun", - width: 11, - distance: 0, - startAngle: 0, - rotateSpeed: 0, - color: "rgb(255,180,40)", - }, - { - name: "Mercure", - type: "planet", - width: 0.32, - distance: 6.4, - rotateSpeed: 1.7, - color: "rgb(180, 144, 88)", - origin: "Sun", - }, - { - name: "Earth", - type: "planet", - width: 0.95, - distance: 11.2, - rotateSpeed: 1, - color: "rgb(19,102,150)", - origin: "Sun", - }, - { - name: "Moon", - type: "planet", - width: 0.24, - distance: 1.6, - rotateSpeed: 2, - color: "rgb(200, 200, 200)", - origin: "Earth", - }, - { - name: "Mars", - type: "planet", - width: 0.64, - distance: 16, - rotateSpeed: 0.66, - color: "rgb(233, 88, 26)", - origin: "Sun", - }, - { - name: "Jupiter", - type: "planet", - width: 2.4, - distance: 20, - rotateSpeed: 0.46, - color: "rgb(169, 109, 45)", - origin: "Sun", - }, - { - name: "Saturne", - type: "planet", - width: 1.6, - distance: 28, - rotateSpeed: 0.36, - color: "rgb(164,127,84)", - origin: "Sun", - }, - { - name: "Uranus", - type: "planet", - width: 0.64, - distance: 30, - rotateSpeed: 0.33, - color: "rgb(84,149,164)", - origin: "Sun", - }, - { - name: "Netpune", - type: "planet", - width: 0.48, - distance: 33.5, - rotateSpeed: 0.3, - color: "rgb(36,82,154)", - origin: "Sun", - }, -]; diff --git a/src/utils/generateComet.ts b/src/utils/generateComet.ts index 3eee15c..e129349 100644 --- a/src/utils/generateComet.ts +++ b/src/utils/generateComet.ts @@ -1,28 +1,10 @@ -import { parseColor } from "src/utils/parseColor"; - export const generateComet = ({ frequence }: { frequence: number }) => { return new Array(frequence).fill(0).flatMap(() => { - const ref = "cometRef_" + Math.random(); return [ { - name: ref, - type: "galaxy", - width: 0, - distance: 1500, - rotateSpeed: 0.08 + Math.random() * 0.033, - rgb: parseColor("rgb(1, 1, 1)"), - }, - { - name: "Comet_" + Math.random(), - type: "star", - width: 0.15, - distance: 1500 + Math.random() * 50, - rotateSpeed: 0.2 + Math.random() * 0.1, - origin: ref, - rgb: parseColor("#ffffff"), + name: "comet", + frequence, }, ]; }); }; - -console.log(generateComet({ frequence: 1 })); diff --git a/src/utils/generateSolarSytem.ts b/src/utils/generateSolarSytem.ts index b3ced9f..8815ecf 100644 --- a/src/utils/generateSolarSytem.ts +++ b/src/utils/generateSolarSytem.ts @@ -14,7 +14,7 @@ export const generateSolarSytem = ({ { name: "Sun", type: "sun", - width: 4 * 0.5 * scale, + width: 3.8 * 0.5 * scale, distance: distance / 2, startAngle: 0, rotateSpeed: 0.0033 * rotationSpeed, @@ -23,7 +23,7 @@ export const generateSolarSytem = ({ { name: "Mercure", type: "planet", - width: 0.32 * 0.5 * scale, + width: 0.3 * 0.5 * scale, distance: 4.2, rotateSpeed: 0.017 * rotationSpeed, rgb: parseColor("rgb(180, 144, 88)"), @@ -33,7 +33,7 @@ export const generateSolarSytem = ({ name: "Earth", type: "planet", width: 0.96 * 0.5 * scale, - distance: 9.6 * 0.5 * scale, + distance: 10 * 0.5 * scale, rotateSpeed: 0.01 * rotationSpeed, rgb: parseColor("rgb(19,102,150)"), origin: "Sun", @@ -69,26 +69,26 @@ export const generateSolarSytem = ({ name: "Saturne", type: "planet", width: 1.2 * 0.5 * scale, - distance: 24 * 0.5 * scale, - rotateSpeed: 0.0036 * rotationSpeed, + distance: 22 * 0.5 * scale, + rotateSpeed: 0.004 * rotationSpeed, rgb: parseColor("rgb(164,127,84)"), origin: "Sun", }, { name: "Uranus", type: "planet", - width: 0.64 * 0.5 * scale, - distance: 25.6 * 0.5 * scale, - rotateSpeed: 0.0033 * rotationSpeed, + width: 0.76 * 0.5 * scale, + distance: 25.2 * 0.5 * scale, + rotateSpeed: 0.0037 * rotationSpeed, rgb: parseColor("rgb(84,149,164)"), origin: "Sun", }, { name: "Netpune", type: "planet", - width: 0.42 * 0.5 * scale, + width: 0.62 * 0.5 * scale, distance: 27.2 * 0.5 * scale, - rotateSpeed: 0.003 * rotationSpeed, + rotateSpeed: 0.0033 * rotationSpeed, rgb: parseColor("rgb(36,82,154)"), origin: "Sun", },