Add (nonfunctional) rulers and scrollbars to the viewport (#279)

* Add nonfunctional rulers and scrollbars to viewport

* Switch from DOM divs to SVG lines

* Switch from SVG lines to a single SVG path

* Change variable names
This commit is contained in:
Keavon Chambers
2021-07-17 16:11:52 -07:00
parent f5bb235ae0
commit b650658810
3 changed files with 320 additions and 8 deletions

View File

@@ -0,0 +1,113 @@
<template>
<div class="canvas-ruler" :class="direction.toLowerCase()" ref="rulerRef">
<svg :style="svgBounds">
<path :d="svgPath" />
</svg>
</div>
</template>
<style lang="scss">
.canvas-ruler {
flex: 1 1 100%;
background: var(--color-5-dullgray);
overflow: hidden;
position: relative;
&.vertical {
width: 16px;
}
&.horizontal {
height: 16px;
}
svg {
position: absolute;
path {
stroke-width: 1px;
stroke: var(--color-7-middlegray);
}
}
}
</style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
const RULER_THICKNESS = 16;
export enum RulerDirection {
"Horizontal" = "Horizontal",
"Vertical" = "Vertical",
}
export default defineComponent({
props: {
direction: { type: String as PropType<RulerDirection>, default: RulerDirection.Vertical },
origin: { type: Number, required: true },
majorMarkSpacing: { type: Number, required: true },
mediumDivisions: { type: Number, default: 5 },
minorDivisions: { type: Number, default: 2 },
},
computed: {
svgPath(): string {
const isVertical = this.direction === RulerDirection.Vertical;
const lineDirection = isVertical ? "H" : "V";
let offsetStart = this.origin % this.majorMarkSpacing;
if (offsetStart < this.majorMarkSpacing) offsetStart -= this.majorMarkSpacing;
const divisions = this.majorMarkSpacing / this.mediumDivisions / this.minorDivisions;
const majorMarksFrequency = this.mediumDivisions * this.minorDivisions;
let dPathAttribute = "";
let i = 0;
for (let location = offsetStart; location < this.rulerLength; location += divisions) {
let length = RULER_THICKNESS / 4;
if (i % majorMarksFrequency === 0) length = RULER_THICKNESS;
else if (i % this.minorDivisions === 0) length = RULER_THICKNESS / 2;
i += 1;
const destination = Math.round(location) + 0.5;
const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;
dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;
}
return dPathAttribute;
},
},
methods: {
handleResize() {
if (!this.$refs.rulerRef) return;
const rulerElement = this.$refs.rulerRef as HTMLElement;
const isVertical = this.direction === RulerDirection.Vertical;
const newLength = isVertical ? rulerElement.clientHeight : rulerElement.clientWidth;
const roundedUp = (Math.floor(newLength / this.majorMarkSpacing) + 1) * this.majorMarkSpacing;
if (roundedUp !== this.rulerLength) {
this.rulerLength = roundedUp;
const thickness = `${RULER_THICKNESS}px`;
const length = `${roundedUp}px`;
this.svgBounds = isVertical ? { width: thickness, height: length } : { width: length, height: thickness };
}
},
},
mounted() {
window.addEventListener("resize", this.handleResize);
this.handleResize();
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
data() {
return {
rulerLength: 0,
svgBounds: { width: "0px", height: "0px" },
RulerDirection,
};
},
});
</script>

View File

@@ -0,0 +1,148 @@
<template>
<div class="persistent-scrollbar" :class="direction.toLowerCase()">
<button class="arrow decrease"></button>
<div class="scroll-track">
<div class="scroll-click-area decrease" :style="[trackStart, preThumb, sides]"></div>
<div class="scroll-thumb" :style="[thumbStart, thumbEnd, sides]"></div>
<div class="scroll-click-area increase" :style="[postThumb, trackEnd, sides]"></div>
</div>
<button class="arrow increase"></button>
</div>
</template>
<style lang="scss">
.persistent-scrollbar {
display: flex;
flex: 1 1 100%;
.arrow {
flex: 0 0 auto;
display: block;
background: none;
outline: none;
border: none;
border-style: solid;
width: 0;
height: 0;
padding: 0;
}
.scroll-track {
flex: 1 1 100%;
position: relative;
.scroll-thumb {
position: absolute;
border-radius: 4px;
background: var(--color-5-dullgray);
&:hover {
background: var(--color-6-lowergray);
}
}
.scroll-click-area {
position: absolute;
}
}
&.vertical {
flex-direction: column;
.arrow.decrease {
margin: 4px 3px;
border-width: 0 5px 8px 5px;
border-color: transparent transparent var(--color-5-dullgray) transparent;
&:hover {
border-color: transparent transparent var(--color-6-lowergray) transparent;
}
}
.arrow.increase {
margin: 4px 3px;
border-width: 8px 5px 0 5px;
border-color: var(--color-5-dullgray) transparent transparent transparent;
&:hover {
border-color: var(--color-6-lowergray) transparent transparent transparent;
}
}
}
&.horizontal {
flex-direction: row;
.arrow.decrease {
margin: 3px 4px;
border-width: 5px 8px 5px 0;
border-color: transparent var(--color-5-dullgray) transparent transparent;
&:hover {
border-color: transparent var(--color-6-lowergray) transparent transparent;
}
}
.arrow.increase {
margin: 3px 4px;
border-width: 5px 0 5px 8px;
border-color: transparent transparent transparent var(--color-5-dullgray);
&:hover {
border-color: transparent transparent transparent var(--color-6-lowergray);
}
}
}
}
</style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
export enum ScrollbarDirection {
"Horizontal" = "Horizontal",
"Vertical" = "Vertical",
}
export default defineComponent({
props: {
direction: { type: String as PropType<ScrollbarDirection>, default: ScrollbarDirection.Vertical },
},
computed: {
trackStart(): { left: string } | { top: string } {
return this.direction === ScrollbarDirection.Vertical ? { top: "0%" } : { left: "0%" };
},
preThumb(): { right: string } | { bottom: string } {
const start = 25;
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${100 - start}%` } : { right: `${100 - start}%` };
},
thumbStart(): { left: string } | { top: string } {
const start = 25;
return this.direction === ScrollbarDirection.Vertical ? { top: `${start}%` } : { left: `${start}%` };
},
thumbEnd(): { right: string } | { bottom: string } {
const end = 25;
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${end}%` } : { right: `${end}%` };
},
postThumb(): { left: string } | { top: string } {
const end = 25;
return this.direction === ScrollbarDirection.Vertical ? { top: `${100 - end}%` } : { left: `${100 - end}%` };
},
trackEnd(): { right: string } | { bottom: string } {
return this.direction === ScrollbarDirection.Vertical ? { bottom: "0%" } : { right: "0%" };
},
sides(): { left: string; right: string } | { top: string; bottom: string } {
return this.direction === ScrollbarDirection.Vertical ? { left: "0%", right: "0%" } : { top: "0%", bottom: "0%" };
},
},
data() {
return {
ScrollbarDirection,
};
},
});
</script>