diff --git a/.changeset/mighty-planes-call.md b/.changeset/mighty-planes-call.md new file mode 100644 index 0000000..8e789a4 --- /dev/null +++ b/.changeset/mighty-planes-call.md @@ -0,0 +1,5 @@ +--- +"scroll-svg": patch +--- + +JSDoc Added diff --git a/.npmignore b/.npmignore index 8f6de9b..5dc296d 100644 --- a/.npmignore +++ b/.npmignore @@ -4,5 +4,5 @@ src pnpm-lock.yaml tsconfig.json vite.config.ts -examples +example docs \ No newline at end of file diff --git a/README.md b/README.md index bf6b4c0..2845a08 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ yarn add scroll-svg To draw the svg path, import `scrollSVG` and pass the svg path element to `scrollSVG`. ```javascript -import scrollSVG from "scroll-svg" +import scrollSVG from 'scroll-svg' -const svgPath = document.querySelector("#scroll-line") +const svgPath = document.querySelector('#scroll-line') const svg = scrollSVG(svgPath) ``` @@ -57,13 +57,13 @@ const svg = scrollSVG(svgPath) To use with Typescript, change it from the implicit `Element|null` to `SVGPathElement` type before passing it to `scrollSVG`. ```typescript -const svgPath = document.querySelector("#scroll-line") as SVGPathElement +const svgPath = document.querySelector('#scroll-line') as SVGPathElement // ^^^^^^^^^^^^^^^^^ ``` ### Stop the animation -To stop the svg path, use the .stopAnimating() method on the svg object. +To stop the svg path animation, use the .stopAnimating() method on the svg object. ```javascript svg.stopAnimating() @@ -71,7 +71,7 @@ svg.stopAnimating() ### Reactivate the animation -To continue the svg path, use the .animate() method on the svg object. +To continue the svg path animation after it was stopped, use the .animate() method on the svg object. ```javascript svg.animate() @@ -88,7 +88,7 @@ These are the default options. ```javascript const options = { invert: false, - draw_origin: "center", + draw_origin: 'center', offset: 0, speed: 1, undraw: false, @@ -106,7 +106,7 @@ const svg = scrollSVG(svg, options) It is not required to use all of the options. You can pass just the options you need and leave the others out like in the example below. ```javascript -const svg = scrollSVG(svg, { invert: true, draw_origin: "center" }) +const svg = scrollSVG(svg, { invert: true, draw_origin: 'center' }) ``` ## Changing options after initialization @@ -167,7 +167,7 @@ Default Value: `1` ### Undraw -The `undraw` option allows you to control whether the svg will be drawn or undrawn on scroll. If the value is `true`, the svg will be undrawn on scroll. If the value is `false`, the svg will be drawn on scroll. The default value is `false` which means the svg will be drawn on scroll. It is useful if you want to draw the svg on scroll but undraw it when the user scrolls back up. +The `undraw` option allows you to control whether the svg will be drawn or undrawn on scroll. If the value is `true`, the svg will be undrawn on scroll. If the value is `false`, the svg will be drawn on scroll. The default value is `false` which means the svg will be drawn on scroll. It is useful if you want to draw the svg on scroll but undraw it when the user scrolls back up. (Use the `.changeOptions()` for that)

Valid Values: `true` or `false` @@ -244,7 +244,7 @@ To use ScrollSvg with React, you can use the `useEffect` hook to start animating ```javascript useEffect(() => { - const svgPath = document.querySelector("#scroll-line") + const svgPath = document.querySelector('#scroll-line') const svg = scrollSVG(svgPath) return () => svg.remove() diff --git a/example/example.css b/example/example.css index d60089d..20611c9 100644 --- a/example/example.css +++ b/example/example.css @@ -103,3 +103,7 @@ section:nth-of-type(2n) { width: auto; height: 100%; } + +#scroll-line-2 { + transition: all 0s ease; +} diff --git a/example/example.ts b/example/example.ts index 26100f4..6324ced 100644 --- a/example/example.ts +++ b/example/example.ts @@ -1,11 +1,11 @@ -import scrollSvg from "../src/index" +import scrollSvg from '../src/index' -const svgPath1 = document.querySelector("#scroll-line-1") as SVGPathElement -const svgPath2 = document.querySelector("#scroll-line-2") as SVGPathElement -const svgPath3 = document.querySelector("#scroll-line-3") as SVGPathElement -const svgPath4 = document.querySelector("#scroll-line-4") as SVGPathElement +const svgPath1 = document.querySelector('#scroll-line-1') as SVGPathElement +const svgPath2 = document.querySelector('#scroll-line-2') as SVGPathElement +const svgPath3 = document.querySelector('#scroll-line-3') as SVGPathElement +const svgPath4 = document.querySelector('#scroll-line-4') as SVGPathElement const svg1 = scrollSvg(svgPath1, { invert: true }) -const svg2 = scrollSvg(svgPath2, { draw_origin: "bottom" }) +const svg2 = scrollSvg(svgPath2, { draw_origin: 'bottom' }) const svg3 = scrollSvg(svgPath3, { offset: 100 }) const svg4 = scrollSvg(svgPath4, { undraw: true }) diff --git a/package.json b/package.json index 1e4d382..147d1a7 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "keywords": [ "scroll", "svg", - "animations" + "animations", + "animate svg", + "scroll animation" ], "author": "Daniel Pulber", "license": "MIT", diff --git a/src/index.ts b/src/index.ts index d0fe5e7..986b4c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,20 @@ -import { scrollSvgClass, scrollSvgClassEmpty } from "./scrollSvgClass" -import { Options, OptionalOptions } from "./types" -import { validateOptions, validSvgPath } from "./utils/inputValidation" +import { scrollSvgClass, scrollSvgClassEmpty } from './scrollSvgClass' +import { Options, OptionalOptions } from './types' +import { validateOptions, validSvgPath } from './utils/inputValidation' export const defaultOptions: Options = { invert: false, - draw_origin: "center", + draw_origin: 'center', offset: 0, speed: 1, undraw: false, } +/** + * + * @param {SVGPathElement} svgPath The SVG Path which you wish to animate on scroll + * @param {OptionalOptions} userOptions Options to customize how and when the SVG is drawn + */ export default function scrollSvg(svgPath: SVGPathElement, userOptions: OptionalOptions = defaultOptions) { // validate svgPath // if invalid returns true, the function returns an empty replica class of scrollSvgClass diff --git a/src/scrollSvgClass copy.ts b/src/scrollSvgClass copy.ts deleted file mode 100644 index 2d2ff3d..0000000 --- a/src/scrollSvgClass copy.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { defaultOptions } from "." -import { OptionalOptions, Options } from "./types" -import calcPercentToDraw from "./utils/calcPercentToDraw" -import calcAndDrawScrollLine from "./utils/calcAndDrawScrollLine" -import { validateOptions } from "./utils/inputValidation" -import setupSvgPath from "./utils/minor/setupSvgPath" - -export class scrollSvgClass { - private svgPath: SVGPathElement - private options: Options - private animationFrame: number = 0 - private prevBoundingRectTop: number - private isActive: boolean = true - - constructor(svgPath: SVGPathElement, options: Options) { - // initialize class variables - this.svgPath = svgPath - this.options = options - this.prevBoundingRectTop = svgPath.getBoundingClientRect().top - - // initialize svgPath - setupSvgPath(svgPath) - calcAndDrawScrollLine(svgPath, options) - - //start animating - animationFrame(this) - } - - animate() { - if (this.isActive) return - this.isActive = true - animationFrame(this) - } - - stopAnimating() { - this.isActive = false - this.animationFrame = 0 - } - - redraw() { - calcAndDrawScrollLine(this.svgPath, this.options) - } - - changeOptions(userOptions: OptionalOptions) { - const options = { ...this.options, ...userOptions } - - if (validateOptions(options, userOptions) > 0) return false - this.options = options - return true - } - - getOptions() { - return this.options - } - getSvgPath() { - return this.svgPath - } - getPercentageDrawn() { - if (this.options.undraw) return 100 * (1 - calcPercentToDraw(this.svgPath, this.options)) - return 100 * calcPercentToDraw(this.svgPath, this.options) - } - - clear() { - this.svgPath.style.strokeDashoffset = `${this.svgPath.getTotalLength()}` - } - fill() { - this.svgPath.style.strokeDashoffset = "0" - } -} - -const animationFrame = (scrollSvgObj: any) => { - // check if user has scrolled - if (scrollSvgObj.prevBoundingRectTop !== scrollSvgObj.svgPath.getBoundingClientRect().top) { - calcAndDrawScrollLine(scrollSvgObj.svgPath, scrollSvgObj.options) - scrollSvgObj.prevBoundingRectTop = scrollSvgObj.svgPath.getBoundingClientRect().top - } - - // check if user still wishes to continue animating - if (scrollSvgObj.isActive) { - scrollSvgObj.animationFrame = requestAnimationFrame(function () { - animationFrame(scrollSvgObj) - }) - } else { - cancelAnimationFrame(scrollSvgObj.animationFrame) - } -} - -// an empty replica class of scrollSvgClass to return when the input is invalid -export class scrollSvgClassEmpty { - constructor() {} - animate() {} - stopAnimating() {} - redraw() {} - changeOptions() { - return false - } - getOptions() { - return defaultOptions - } - getSvgPath() { - console.error("Invalid input to scrollSvg. Returning an empty SVGPathElement.") - return document.createElementNS("http://www.w3.org/2000/svg", "path") - } - getPercentageDrawn() { - return 0 - } - clear() {} - fill() {} -} diff --git a/src/scrollSvgClass.ts b/src/scrollSvgClass.ts index 4894fba..6518dd7 100644 --- a/src/scrollSvgClass.ts +++ b/src/scrollSvgClass.ts @@ -1,11 +1,11 @@ -import { defaultOptions } from "./index" -import { OptionalOptions, Options } from "./types" -import calcPercentToDraw from "./utils/calcPercentToDraw" -import calcAndDrawScrollLine from "./utils/calcAndDrawScrollLine" -import { validateOptions } from "./utils/inputValidation" -import setupSvgPath from "./utils/minor/setupSvgPath" +import { defaultOptions } from './index' +import { OptionalOptions, Options, ScrollSvgClass } from './types' +import calcPercentToDraw from './utils/calcPercentToDraw' +import calcAndDrawScrollLine from './utils/calcAndDrawScrollLine' +import { validateOptions } from './utils/inputValidation' +import setupSvgPath from './utils/minor/setupSvgPath' -export class scrollSvgClass { +export class scrollSvgClass implements ScrollSvgClass { svgPath: SVGPathElement options: Options animationFrame: number = 0 @@ -25,8 +25,8 @@ export class scrollSvgClass { calcAndDrawScrollLine(svgPath, options) this.observer = new IntersectionObserver( - (items) => { - items.map((item) => { + items => { + items.map(item => { if (item.isIntersecting) { this.isObservable = true animationFrame(this) @@ -36,7 +36,7 @@ export class scrollSvgClass { }) }, { - rootMargin: "50px 0px", + rootMargin: '50px 0px', } ) @@ -79,7 +79,7 @@ export class scrollSvgClass { this.svgPath.style.strokeDashoffset = `${this.svgPath.getTotalLength()}` } fill() { - this.svgPath.style.strokeDashoffset = "0" + this.svgPath.style.strokeDashoffset = '0' } remove() { this.stopAnimating() @@ -105,8 +105,20 @@ const animationFrame = (scrollSvgObj: scrollSvgClass) => { } // an empty replica class of scrollSvgClass to return when the input is invalid -export class scrollSvgClassEmpty { - constructor() {} +export class scrollSvgClassEmpty implements ScrollSvgClass { + svgPath: SVGPathElement + options: Options = defaultOptions + animationFrame: number = 0 + prevBoundingRectTop: number = 0 + isActive: boolean = true + isObservable: boolean = true + observer: IntersectionObserver + + constructor() { + console.error('Scroll Svg Class Empty ~ Seems to be an error with your input.') + this.svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path') + this.observer = new IntersectionObserver(function () {}) + } animate() {} stopAnimating() {} redraw() {} @@ -117,8 +129,8 @@ export class scrollSvgClassEmpty { return defaultOptions } getSvgPath() { - console.error("Invalid input to scrollSvg. Returning an empty SVGPathElement.") - return document.createElementNS("http://www.w3.org/2000/svg", "path") + console.error('Invalid input to scrollSvg. Returning an empty SVGPathElement.') + return this.svgPath } getPercentageDrawn() { return 0 diff --git a/src/types.ts b/src/types.ts index 83bfc63..470977b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,119 @@ export type OptionalOptions = { + /** + * The `invert` option inverts which direction the svg draws from. Sometimes an svg draws backwards by default and the `invert` option is required to correct it. + * + * Valid Values: `true` or `false`. + * + * Default Value: `false`. + */ invert?: boolean - draw_origin?: "top" | "center" | "bottom" | number + /** + * The `draw_origin` option controls at which point on the screen the svg gets drawn, with `0` being the **top** of the screen and `1` being the **bottom**. By default it draws from the `center` of the screen or at `0.5`. The option takes the values `top` which is `0.25`, `center` which is `0.5`, `bottom` which is `0.75`, or any decimal between `0` and `1`. + * + * Valid Values: `top`, `center`, `bottom`, or any decimal from `0` to `1` + * + * Default Value: `center` + */ + draw_origin?: 'top' | 'center' | 'bottom' | number + /** + * The `offset` option allow you to offset the svg drawing from the `draw_origin` by a set amount of **pixels**. This is useful if you want to draw the svg before it reaches the `draw_origin` or after it passes it. It takes any number as a value. If the value is negative, the svg will be drawn the `offset` amount of pixels behind the `draw_origin` and if the value is positive, the svg will be ahead the `draw_origin` by the `offset` amount. So if you want to draw the svg 100 pixels before the `draw_origin`, you would use `-100` as the value. + * + * Valid Values: any `number`, positive or negative + * + * Default Value: `0` + */ offset?: number + /** + * The `speed` option allows you to control the speed at which the svg is drawn. It takes any number above **zero** as a value. The higher the number, the faster the svg will be drawn. The default value is `1` which is the normal speed. If you want to draw the svg half as fast, you would use `0.5` as the value. It is useful if you want to draw multiple SVGs at different speeds or if you want to draw the svg slower or faster than normal. + * + * Valid Values: any `number` above 0 + * + * Default Value: `1` + */ speed?: number + /** + * he `undraw` option allows you to control whether the svg will be drawn or undrawn on scroll. If the value is `true`, the svg will be undrawn on scroll. If the value is `false`, the svg will be drawn on scroll. The default value is `false` which means the svg will be drawn on scroll. It is useful if you want to draw the svg on scroll but undraw it when the user scrolls back up. (Use the `.changeOptions()` for that) + * + * Valid Values: `true` or `false` + * + * Default Value: `false` + */ undraw?: boolean } export type Options = Required + +export interface ScrollSvgClass { + /** + * The SVG Path being animated + */ + svgPath: SVGPathElement + /** + * The current options + */ + options: Options + /** + * Used in the animationFrame function + */ + animationFrame: number + /** + * Used for performance when deciding wether or not to recalculated the SVG Path + */ + prevBoundingRectTop: number + /** + * Whether the SVG is currently being animated + */ + isActive: boolean + /** + * Whether the SVG Path is currently observed by the IntersectionObserver. + */ + isObservable: boolean + /** + * An instance of an IntersectionObserver. Used for performance. + */ + observer: IntersectionObserver + + /** + * To continue the svg path animation after it was stopped, use the .animate() method on the svg object. + */ + animate(): void + /** + * To stop the svg path animation, use the .stopAnimating() method on the svg object. + */ + stopAnimating(): void + /** + * To redraw the svg, use the `.redraw()` method on the svg object. This is useful if want the svg to be redrawn before next scroll event. + */ + redraw(): void + /** + * To change the options after initialization, use the `.changeOptions()` method on the svg object. This can be useful if you want to change the options after the user has scrolled to a certain point. For example, if you want to change the `undraw` option to `true` after the user has scrolled past the svg and have the svg follow the user as they scroll back up. + * + * The `.changeOptions()` method also returns `true` if the options were changed successfully and `false` if they were not. Also, the svg won't be redrawn until the next scroll event. So if you you want the svg to be updated with the new options immediately, you can use the `.redraw()` method. + * @param userOptions + */ + changeOptions(userOptions: OptionalOptions): boolean + /** + * Get the current options + */ + getOptions(): Options + /** + * This returns the svg path element that was passed to `scrollSVG`. + */ + getSvgPath(): SVGPathElement + /** + * To get the percentage of the svg path that has been drawn, use the `.getPercentage()` method on the svg object. This returns a number between `0` and `100`. The percentage doesn't take into account the `offset` option, so if the svg is drawn 50% of the way and the `offset` is 100 pixels, the percentage will still be 50%. Also note that the if undraw is `true`, the percentage will still reflect the percentage of the svg path that has been drawn, not the percentage of the svg that has been scrolled past. + */ + getPercentageDrawn(): number + /** + * This makes the svg path disappear. It will be draw again when the user scrolls, so use `stopAnimating()` if you don't want it to be drawn again. + */ + clear(): void + /** + * This draws the svg path completely. It will be drawn back to the scroll position when the user scrolls, so use `stopAnimating()` if you don't want it to be drawn back to the scroll position. + */ + fill(): void + /** + * Use the `.remove()` method to delete any listeners for the svg path. This is useful if you want to stop animating the svg path when the component unmounts. + */ + remove(): void +} diff --git a/src/utils/minor/changeTransition.ts b/src/utils/minor/changeTransition.ts new file mode 100644 index 0000000..fa3825f --- /dev/null +++ b/src/utils/minor/changeTransition.ts @@ -0,0 +1,19 @@ +export default function changeTransition(svgPath: SVGPathElement): void { + const svgPathLength = svgPath.getTotalLength() + svgPath.style.strokeDasharray = svgPathLength + " " + svgPathLength + svgPath.style.strokeDashoffset = svgPathLength + "" + + if (svgPath.style.transition === "") { + console.log("empty") + svgPath.style.transition = " " + } + + svgPath.style.transition = `${svgPath.style.transition}, stroke-dashoffset 25ms ease-in-out` + // svgPath.style.transition = `all 50ms ease, stroke-dashoffset 25ms ease-in-out` + + const regex = new RegExp("stroke-dashoffset") + if (regex.test(svgPath.style.transition)) { + console.log("true") + } + console.log(svgPath.style.transition) +} diff --git a/src/utils/minor/setupSvgPath.ts b/src/utils/minor/setupSvgPath.ts index 8d5064a..7635de7 100644 --- a/src/utils/minor/setupSvgPath.ts +++ b/src/utils/minor/setupSvgPath.ts @@ -1,5 +1,9 @@ +import changeTransition from "./changeTransition" + export default function setupSvgPath(svgPath: SVGPathElement): void { const svgPathLength = svgPath.getTotalLength() svgPath.style.strokeDasharray = svgPathLength + " " + svgPathLength svgPath.style.strokeDashoffset = svgPathLength + "" + + changeTransition(svgPath) }