@@ -31,6 +31,7 @@ const redoStack = ref([]);
3131const viewBox = ref (" 0 0 0 0" );
3232
3333const currentColor = ref (props .color )
34+ const strokeWidth = ref (1 );
3435
3536const buttonBorderColor = computed (() => {
3637 return lightenHexColor (props .color , 0.6 );
@@ -86,33 +87,51 @@ function startDrawing(event) {
8687
8788function draw (event ) {
8889 if (! isDrawing .value || ! svgElement .value ) return ;
89-
9090 const { x , y } = getRelativePosition (event );
9191 currentPath .value += ` ${ x} ${ y} ` ;
9292}
9393
9494function smoothPath (path ) {
9595 const segments = path .trim ().split (/ \s + / );
96- if (segments .length < 4 ) return path;
97- const smoothedPath = [segments[0 ], segments[1 ], segments[2 ]]; // Keep M x y incipit
98- for (let i = 3 ; i < segments .length - 2 ; i += 2 ) {
99- const x1 = parseFloat (segments[i - 2 ]);
100- const y1 = parseFloat (segments[i - 1 ]);
101- const x2 = parseFloat (segments[i]);
102- const y2 = parseFloat (segments[i + 1 ]);
103- if (isNaN (x1) || isNaN (y1) || isNaN (x2) || isNaN (y2)) {
104- continue
105- }
96+ if (segments .length < 4 ) {
97+ return path;
98+ }
99+ const coordinates = segments .slice (1 ).map (Number );
100+ if (coordinates .length % 2 !== 0 ) {
101+ return path;
102+ }
103+ const smoothedCoordinates = reduceNoise (coordinates);
104+ const smoothedPath = [` M ${ smoothedCoordinates[0 ]} ${ smoothedCoordinates[1 ]} ` ]; // Keep M x y incipit
105+ for (let i = 2 ; i < smoothedCoordinates .length - 2 ; i += 2 ) {
106+ const x1 = smoothedCoordinates[i - 2 ];
107+ const y1 = smoothedCoordinates[i - 1 ];
108+ const x2 = smoothedCoordinates[i];
109+ const y2 = smoothedCoordinates[i + 1 ];
106110 const controlX = (x1 + x2) / 2 ;
107111 const controlY = (y1 + y2) / 2 ;
108112 smoothedPath .push (` Q ${ x1} ${ y1} ${ controlX} ${ controlY} ` );
109113 }
110- const lastX = segments[ segments .length - 2 ];
111- const lastY = segments[ segments .length - 1 ];
114+ const lastX = smoothedCoordinates[ smoothedCoordinates .length - 2 ];
115+ const lastY = smoothedCoordinates[ smoothedCoordinates .length - 1 ];
112116 smoothedPath .push (` L ${ lastX} ${ lastY} ` );
113117 return smoothedPath .join (" " );
114118}
115119
120+ function reduceNoise (coordinates , smoothingFactor = 1 ) {
121+ const smoothed = [... coordinates];
122+ for (let i = 2 ; i < coordinates .length - 2 ; i += 2 ) {
123+ const x = coordinates[i];
124+ const y = coordinates[i + 1 ];
125+ const prevX = coordinates[i - 2 ];
126+ const prevY = coordinates[i - 1 ];
127+ const nextX = coordinates[i + 2 ];
128+ const nextY = coordinates[i + 3 ];
129+ smoothed[i] = x + smoothingFactor * ((prevX + nextX) / 2 - x);
130+ smoothed[i + 1 ] = y + smoothingFactor * ((prevY + nextY) / 2 - y);
131+ }
132+ return smoothed;
133+ }
134+
116135function optimizeSvgPath (path ) {
117136 const commands = path .trim ().split (/ \s + / );
118137 let optimizedPath = ' ' ;
@@ -181,7 +200,11 @@ function optimizeSvgPath(path) {
181200
182201function stopDrawing () {
183202 if (isDrawing .value ) {
184- stack .value .push (optimizeSvgPath (smoothPath (currentPath .value )));
203+ stack .value .push ({
204+ strokeWidth: strokeWidth .value ,
205+ path: currentPath .value ,
206+ color: currentColor .value
207+ });
185208 redoStack .value = [];
186209 currentPath .value = " " ;
187210 }
@@ -228,6 +251,9 @@ function reset() {
228251 stack .value = [];
229252 redoStack .value = [];
230253}
254+
255+ const range = ref (null );
256+
231257 </script >
232258
233259<template >
@@ -299,6 +325,18 @@ function reset() {
299325 >
300326 <BaseIcon name =" trash" :stroke =" color" />
301327 </button >
328+ <input
329+ ref =" range"
330+ type =" range"
331+ class =" vertical-range"
332+ :min =" 0.5"
333+ :max =" 12"
334+ :step =" 0.1"
335+ v-model =" strokeWidth"
336+ :style =" {
337+ accentColor: color
338+ }"
339+ />
302340 </div >
303341 <svg
304342 ref =" svgElement"
@@ -316,8 +354,11 @@ function reset() {
316354 @touchmove.prevent =" draw"
317355 @touchend =" stopDrawing"
318356 >
319- <path class =" vue-ui-pen-and-paper-path" v-for =" path in stack" :key =" path" :d =" path" :stroke =" currentColor" fill =" none" />
320- <path class =" vue-ui-pen-and-paper-path vue-ui-pen-and-paper-path-drawing" v-if =" isDrawing" :d =" currentPath" :stroke =" currentColor" fill =" none" />
357+ <template v-for =" path in stack " :key =" path " >
358+ <circle v-if =" path.path.replace('M', '').split(' ').length === 2" :cx =" path.path.replace('M', '').split(' ')[0]" :cy =" path.path.replace('M', '').split(' ')[1]" :r =" path.strokeWidth / 2" :fill =" path.color" />
359+ <path v-else class =" vue-ui-pen-and-paper-path" :d =" path.path" :stroke =" path.color" :stroke-width =" path.strokeWidth" fill =" none" />
360+ </template >
361+ <path class =" vue-ui-pen-and-paper-path vue-ui-pen-and-paper-path-drawing" v-if =" isDrawing" :d =" currentPath" :stroke =" currentColor" :stroke-width =" strokeWidth * 1.1" fill =" none" />
321362 </svg >
322363</template >
323364
@@ -366,7 +407,13 @@ function reset() {
366407 stroke-linecap : round ;
367408 stroke-linejoin : round ;
368409}
369- .vue-ui-pen-and-paper-path-drawing {
370- stroke-width : 2 ;
410+
411+ input [type = " range" ].vertical-range {
412+ writing-mode : vertical-lr ;
413+ position : absolute ;
414+ top : 50% ;
415+ transform : translateY (-50% ) rotate (180deg );
416+ left : 36px ;
371417}
418+
372419 </style >
0 commit comments