Skip to content

Commit a4c434c

Browse files
committed
Update README, write comments
1 parent 8a4bf5b commit a4c434c

File tree

7 files changed

+130
-38
lines changed

7 files changed

+130
-38
lines changed

README.md

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,100 @@
22

33
**[Demo drawing app](https://lazybrush.dulnan.net)**
44

5-
[Example repository](https://github.com/dulnan/lazy-brush-demo)
5+
__The demo app also uses
6+
[catenary-curve](https://github.com/dulnan/catenary-curve) to draw the little
7+
"rope" between mouse and brush.__
68

79
This library provides the math required to implement a "lazy brush".
810
It takes a radius and the {x,y} coordinates of a mouse/pointer and calculates
911
the position of the brush.
12+
1013
The brush will only move when the pointer is outside the "lazy area" of the
1114
brush. With this technique it's possible to freely draw smooth lines and curves
1215
with just a mouse or finger.
1316

14-
# How it works
17+
## How it works
18+
1519
When the position of the pointer is updated, the distance to the brush is
1620
calculated. If this distance is larger than the defined radius, the brush will
1721
be moved by `distance - radius` pixels in the direction where the pointer is.
1822

19-
# Usage
23+
## Usage
24+
2025
lazy-brush is on npm so you can install it with your favorite package manager.
2126

2227
lazy-brush can be easily added in any canvas drawing scenario. It acts like a
2328
"proxy" between user input and drawing.
2429

30+
It exports a `LazyBrush` class. Create a single instance of the class:
31+
2532
```javascript
2633
const lazy = new LazyBrush({
2734
radius: 30,
2835
enabled: true,
2936
initialPoint: { x: 0, y: 0 }
30-
}) // default
37+
})
38+
```
3139

32-
lazy.update({ x: 50, y: 0 })
40+
You can now use the `update()` method whenever the position of the mouse (or
41+
touch) changes:
42+
43+
```javascript
44+
// Move mouse 20 pixels to the right.
45+
lazy.update({ x: 20, y: 0 })
46+
// Brush is not moved, because 20 is less than the radius (30).
3347
console.log(lazy.getBrushCoordinates()) // { x: 0, y: 0 }
3448

35-
lazy.update({ x: 200, y: 50 })
36-
console.log(lazy.getBrushCoordinates()) // { x: 100, y: 0 }
49+
// Move mouse 40 pixels to the right.
50+
lazy.update({ x: 40, y: 0 })
51+
// Brush is now moved by 10 pixels because 40 (mouse X) - 30 (radius) = 10.
52+
console.log(lazy.getBrushCoordinates()) // { x: 10, y: 0 }
3753
```
3854

39-
Use the `LazyBrush.update()` function to update the pointer position. That
40-
would be when the mouse is moved. The function returns a boolean to indicate
41-
whether any of the values (brush or pointer) have changed. This can be used to
42-
prevent unneccessary canvas redrawing.
55+
The function returns a boolean to indicate whether any of the values (brush or
56+
pointer) have changed. This can be used to prevent unneccessary canvas
57+
redrawing.
58+
4359
If you need to know if the position of the brush was changed, you can get that
44-
boolean via LazyBrush.brushHasMoved().
60+
boolean via `LazyBrush.brushHasMoved()`. Use this information to decide if you
61+
need to redraw the brush on the canvas.
4562

46-
To get the coordinates , use `LazyBrush.getBrushCoordinates()` or
47-
`LazyBrush.getPointerCoordinates()`. This will return an object with x and y
48-
properties.
63+
To get the current brush coordinates, use `LazyBrush.getBrushCoordinates()`.
64+
For the pointer coordinates use `LazyBrush.getPointerCoordinates()`. This will
65+
return a `Point` object with x and y properties.
4966

5067
The functions `getBrush()` and `getPointer()` will return a `LazyPoint`, which
51-
has some functions like getDistanceTo, getAngleTo or equalsTo.
68+
has some additional functions like `getDistanceTo`, `getAngleTo` or `equalsTo`.
69+
70+
### With Friction
71+
72+
You can also pass a friction value (number between 0 and 1) when calling `update()`:
73+
74+
```javascript
75+
lazy.update({ x: 40, y: 0 }, { friction: 0.5 })
76+
```
77+
78+
This will reduce the speed at which the brush moves towards the pointer. A
79+
value of 0 means "no friction", which is the same as not passing a value. 1
80+
means "inifinte friction", the brush won't move at all.
81+
82+
You can define a constant value or make it dynamic, for example using a pressur
83+
value from a touch event.
84+
85+
### Updating both values
86+
87+
You can also update the pointer and the brush coordinates at the same time:
88+
89+
```javascript
90+
lazy.update({ x: 40, y: 0 }, { both: true })
91+
```
5292

53-
# Example
93+
This can be used when supporting touch events: On touch start you would update
94+
both the pointer and the brush so that the pointer can be "moved" away from the
95+
brush until the lazy radius is reached. This is how it's implemented in the
96+
demo page.
5497

55-
## Performance
56-
For performance reasons it's best to decouple calculations and canvas rendering
57-
from mousemove/touchmove events: One way is to store the current coordinates in
58-
a variable. Then, using an animation loop (typically requestAnimationFrame),
59-
call the `LazyBrush.update()` function on every frame with the latest
60-
coordinates from the variable.
98+
## Examples
6199

62-
The library will only do calculations if the pointer or brush values changed.
100+
Check out the [basic example](examples/basic.html) for a simple starting point
101+
on how to use this library.

demo-src/App.vue

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
<Slider
1919
v-model="friction"
2020
label="Friction"
21+
:value-text="frictionValueText"
2122
id="friction"
22-
description="Makes the brush lag behind the cursor. 100 = no lag, 1 = extreme lag."
23-
:min="1"
24-
:max="100"
23+
description="Makes the brush lag behind the cursor. 0 = no lag, 1 = infinite lag."
24+
:min="0"
25+
:max="99"
2526
:disabled="!enabled"
2627
/>
2728
<Slider
@@ -55,7 +56,7 @@
5556
</template>
5657

5758
<script setup lang="ts">
58-
import { ref } from 'vue'
59+
import { computed, ref } from 'vue'
5960
import Sidebar from './components/Sidebar.vue'
6061
import Scene from './components/Scene.vue'
6162
import Slider from './components/Slider.vue'
@@ -64,9 +65,13 @@ import Toggle from './components/Toggle.vue'
6465
6566
const brushRadius = ref(12.5)
6667
const lazyRadius = ref(60)
67-
const friction = ref(90)
68+
const friction = ref(10)
6869
const enabled = ref(true)
6970
const clear = ref(0)
7071
72+
const frictionValueText = computed(() => {
73+
return (friction.value / 100).toFixed(2)
74+
})
75+
7176
const container = ref(null as HTMLDivElement)
7277
</script>

demo-src/components/Slider.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ const props = defineProps({
5858
type: Number,
5959
default: 0
6060
},
61+
valueText: {
62+
type: String,
63+
default: ''
64+
},
6165
disabled: {
6266
type: Boolean,
6367
default: false
@@ -69,7 +73,7 @@ const step = computed(() => {
6973
})
7074
7175
const value = computed(() => {
72-
return Math.round(props.modelValue).toString()
76+
return props.valueText || Math.round(props.modelValue).toString()
7377
})
7478
7579
function getSliderValue(e: Event) {

examples/basic.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
canvas.width = window.innerWidth
3434
canvas.height = window.innerHeight
3535

36+
// While not always reccommended, we can directly draw in your event
37+
// handler here. Generally it is better to do that in an animation loop.
38+
// See the "friction.html" example on how to do that.
3639
canvas.addEventListener('mousemove', function (e) {
3740
const x = e.clientX
3841
const y = e.clientY

examples/friction.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343

4444
function loop() {
4545
// Update the current mouse coordinates.
46+
// When using friction we have to continously call the update method
47+
// because the brush can move even if the cursor position remains the
48+
// same.
4649
lazy.update({ x, y }, { friction: 0.5 })
4750

4851
// Get the updated brush coordinates.

src/LazyBrush.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,62 @@ interface LazyBrushOptions {
99
}
1010

1111
interface LazyBrushUpdateOptions {
12+
/**
13+
* Update both pointer and brush at the same time.
14+
*
15+
* This can be used when supporting touch events: On touch start you would
16+
* update both the pointer and the brush so that the pointer can be "moved"
17+
* away from the brush until the lazy radius is reached.
18+
*/
1219
both?: boolean
20+
21+
/**
22+
* Define the friction (value between 0 and 1) for the brush.
23+
*
24+
* A value of 0 means "no friction" (default when not set). A value of "1"
25+
* means infinite friction (the brush won't move at all).
26+
*/
1327
friction?: number
1428
}
1529

1630
class LazyBrush {
31+
/**
32+
* If the lazy brush should be enabled.
33+
*/
1734
_isEnabled: boolean
35+
36+
/**
37+
* Indicates if the brush has moved in the last update cycle.
38+
*/
1839
_hasMoved: boolean
40+
41+
/**
42+
* The lazy radius.
43+
*/
1944
radius: number
45+
46+
/**
47+
* Coordinates of the pointer.
48+
*/
2049
pointer: LazyPoint
50+
51+
/**
52+
* Coordinates of the brush.
53+
*/
2154
brush: LazyPoint
55+
56+
/**
57+
* The angle between pointer and brush in the last update cycle.
58+
*/
2259
angle: number
23-
distance: number
2460

25-
velocity: LazyPoint
61+
/**
62+
* The distance between pointer and brush in the last update cycle.
63+
*/
64+
distance: number
2665

2766
/**
28-
* constructor
67+
* Constructs a new LazyBrush.
2968
*/
3069
constructor(options: LazyBrushOptions = {}) {
3170
const initialPoint = options.initialPoint || { x: 0, y: 0 }
@@ -34,7 +73,6 @@ class LazyBrush {
3473

3574
this.pointer = new LazyPoint(initialPoint.x, initialPoint.y)
3675
this.brush = new LazyPoint(initialPoint.x, initialPoint.y)
37-
this.velocity = new LazyPoint(initialPoint.x, initialPoint.y)
3876

3977
this.angle = 0
4078
this.distance = 0
@@ -175,8 +213,8 @@ class LazyBrush {
175213

176214
const isOutside = Math.round((this.distance - this.radius) * 10) / 10 > 0
177215
const friction =
178-
options.friction && options.friction < 1
179-
? Math.min(Math.max(options.friction, 0.01), 1)
216+
options.friction && options.friction < 1 && options.friction > 0
217+
? options.friction
180218
: undefined
181219

182220
if (isOutside) {

src/LazyPoint.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export class LazyPoint implements Point {
4242
const angleRotated = angle + Math.PI / 2
4343

4444
if (friction) {
45-
this.x = this.x + Math.sin(angleRotated) * distance * ease(friction)
46-
this.y = this.y - Math.cos(angleRotated) * distance * ease(friction)
45+
this.x = this.x + Math.sin(angleRotated) * distance * ease(1 - friction)
46+
this.y = this.y - Math.cos(angleRotated) * distance * ease(1 - friction)
4747
} else {
4848
this.x = this.x + Math.sin(angleRotated) * distance
4949
this.y = this.y - Math.cos(angleRotated) * distance

0 commit comments

Comments
 (0)