@profitlich/template-toolkit 5.8.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dev/Dev.js +1 -1
- package/dev/toolbar/Toolbar.js +66 -5
- package/dev/toolbar/gridColumns.js +49 -0
- package/dev/toolbar/toolbar.scss +32 -55
- package/package.json +3 -2
- package/dev/toolbar/COLUMNS.md +0 -228
package/dev/Dev.js
CHANGED
package/dev/toolbar/Toolbar.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import GUI from 'lil-gui';
|
|
2
|
+
import { canvasGridLines } from 'canvas-grid-lines';
|
|
2
3
|
import { MediaQueries } from '../../utils/MediaQueries.js';
|
|
4
|
+
import { gridColumnsTriple } from './gridColumns.js';
|
|
3
5
|
import './toolbar.scss';
|
|
4
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Übersetzt die Auswahl im Grid-Menü in die Rasterart von `canvas-grid-lines`.
|
|
9
|
+
* Die Namen der Auswahl bleiben, wie sie waren — sie stehen im gespeicherten
|
|
10
|
+
* State der Toolbar und wären sonst bei jedem ein Rückschritt auf `aus`.
|
|
11
|
+
*
|
|
12
|
+
* Die Farben standen früher als Sass-Variablen in `toolbar.scss`. Ein Canvas
|
|
13
|
+
* bekommt sie zur Laufzeit, deshalb stehen sie jetzt hier.
|
|
14
|
+
*/
|
|
15
|
+
const GRID_MODES = {
|
|
16
|
+
lines: { gridType: 'columns', color: 'rgba(0, 0, 255, 0.5)' },
|
|
17
|
+
ribbons: { gridType: 'ribbons', color: 'rgba(191, 255, 254, 0.5)' },
|
|
18
|
+
};
|
|
19
|
+
|
|
5
20
|
/**
|
|
6
21
|
* Beschreibt eine projekteigene Checkbox in der Dev-Toolbar.
|
|
7
22
|
* @typedef {Object} ToolbarToggle
|
|
@@ -29,12 +44,18 @@ export class Toolbar {
|
|
|
29
44
|
#pictureElements;
|
|
30
45
|
#contentTypeContainer;
|
|
31
46
|
#toggles;
|
|
47
|
+
#config;
|
|
48
|
+
#gridElement;
|
|
49
|
+
#grid = null;
|
|
32
50
|
|
|
33
51
|
/**
|
|
34
52
|
* @param {ToolbarOptions} [options={}] - Projekteigene Ergänzungen.
|
|
53
|
+
* @param {Object} [config={}] - Inhalt von `src/config.json` des Projekts.
|
|
54
|
+
* Das Grid-Overlay leitet Spaltenzahl, Gutter und Seitenränder daraus ab.
|
|
35
55
|
*/
|
|
36
|
-
constructor(options = {}) {
|
|
56
|
+
constructor(options = {}, config = {}) {
|
|
37
57
|
this.#toggles = options.toggles ?? [];
|
|
58
|
+
this.#config = config;
|
|
38
59
|
|
|
39
60
|
// State aus localStorage laden
|
|
40
61
|
const defaults = { visible: false, grid: 'aus', imageSize: false, sizes: false, contentType: false };
|
|
@@ -44,10 +65,12 @@ export class Toolbar {
|
|
|
44
65
|
const saved = localStorage.getItem('devTools');
|
|
45
66
|
this.#state = saved ? { ...defaults, ...JSON.parse(saved) } : defaults;
|
|
46
67
|
|
|
47
|
-
// Grid-Overlay DOM-Element erstellen
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
document.
|
|
68
|
+
// Grid-Overlay DOM-Element erstellen. Das Canvas darin entsteht erst
|
|
69
|
+
// beim ersten Einschalten — es ist eine Bitmap in Viewportgrösse, und
|
|
70
|
+
// die soll nicht zahlen, wer das Raster gar nicht anschaut.
|
|
71
|
+
this.#gridElement = document.createElement('div');
|
|
72
|
+
this.#gridElement.classList.add('dev-toolbar__grid');
|
|
73
|
+
document.body.prepend(this.#gridElement);
|
|
51
74
|
|
|
52
75
|
// Content-Type-Label-Overlay erstellen
|
|
53
76
|
this.#contentTypeContainer = document.createElement('div');
|
|
@@ -94,6 +117,7 @@ export class Toolbar {
|
|
|
94
117
|
|
|
95
118
|
// Event-Listener
|
|
96
119
|
window.addEventListener('resize', this.#onResize);
|
|
120
|
+
window.addEventListener('eventLayoutchange', this.#onLayoutChange);
|
|
97
121
|
document.addEventListener('keydown', this.#handleKeyDown);
|
|
98
122
|
}
|
|
99
123
|
|
|
@@ -109,12 +133,49 @@ export class Toolbar {
|
|
|
109
133
|
#applyState() {
|
|
110
134
|
document.body.setAttribute('data-dev-grid', this.#state.grid);
|
|
111
135
|
document.body.setAttribute('data-dev-content-types', this.#state.contentType);
|
|
136
|
+
this.#updateGrid();
|
|
112
137
|
this.#applyToggles();
|
|
113
138
|
this.#updateImageSize();
|
|
114
139
|
this.#updateSizes();
|
|
115
140
|
this.#updateContentTypeLabels();
|
|
116
141
|
}
|
|
117
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Baut das Raster beim ersten Einschalten und hält es danach auf dem Stand
|
|
145
|
+
* von Auswahl und Layout. Im Modus `aus` geschieht nichts — das Ausblenden
|
|
146
|
+
* erledigt allein `data-dev-grid` in der CSS.
|
|
147
|
+
*/
|
|
148
|
+
#updateGrid() {
|
|
149
|
+
const mode = GRID_MODES[this.#state.grid];
|
|
150
|
+
if (!mode) return;
|
|
151
|
+
|
|
152
|
+
const columns = gridColumnsTriple(this.#config, this.#mediaQueries.layout);
|
|
153
|
+
if (!columns) return;
|
|
154
|
+
|
|
155
|
+
if (!this.#grid) {
|
|
156
|
+
[this.#grid] = canvasGridLines.initGrid({
|
|
157
|
+
targets: this.#gridElement,
|
|
158
|
+
gridType: mode.gridType,
|
|
159
|
+
columns,
|
|
160
|
+
color: mode.color,
|
|
161
|
+
lineWidth: 1,
|
|
162
|
+
units: 'devicepixel',
|
|
163
|
+
}) ?? [];
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Reihenfolge: erst die Spalten, dann die Rasterart. Der gridType-Setter
|
|
168
|
+
// leitet sein Lückenmuster aus dem aktuellen columns-Wert ab, und der
|
|
169
|
+
// muss nach einem Breakpoint-Wechsel schon der neue sein.
|
|
170
|
+
this.#grid.columns = columns;
|
|
171
|
+
this.#grid.gridType = mode.gridType;
|
|
172
|
+
this.#grid.color = mode.color;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#onLayoutChange = () => {
|
|
176
|
+
this.#updateGrid();
|
|
177
|
+
}
|
|
178
|
+
|
|
118
179
|
#updateContentTypeLabels() {
|
|
119
180
|
this.#contentTypeContainer.innerHTML = '';
|
|
120
181
|
if (!this.#state.contentType) return;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rechnet die Spaltenkonfiguration eines Layouts in das Tripel um, das
|
|
3
|
+
* `canvas-grid-lines` für die Rasterarten `columns` und `ribbons` erwartet:
|
|
4
|
+
* `[total, band, gap]` in Rastereinheiten.
|
|
5
|
+
*
|
|
6
|
+
* Die Einheit ist der grösste gemeinsame Teiler von Spaltenbreite und Gutter —
|
|
7
|
+
* aus einem Verhältnis wie 80 : 20 werden damit die ganzen Zahlen 4 : 1, die
|
|
8
|
+
* das Paket als Wiederholungsmuster braucht. Beide Rasterarten teilen sich
|
|
9
|
+
* dieselbe Kantenfolge: `columns` strichelt sie, `ribbons` füllt dazwischen.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} config - Inhalt von `src/config.json` des Projekts.
|
|
12
|
+
* @param {string} layout - Layoutname, etwa `desktop`.
|
|
13
|
+
* @returns {number[]|undefined} `[total, band, gap]`, oder `undefined`, wenn die
|
|
14
|
+
* Angaben fehlen oder sich das Verhältnis nicht ganzzahlig ausdrücken lässt.
|
|
15
|
+
*/
|
|
16
|
+
export function gridColumnsTriple(config, layout) {
|
|
17
|
+
const width = config?.layouts?.[layout];
|
|
18
|
+
const margins = config?.margins?.[layout];
|
|
19
|
+
const gutter = config?.gutter?.[layout];
|
|
20
|
+
const count = config?.columns?.[layout];
|
|
21
|
+
|
|
22
|
+
if (width == null || margins == null || gutter == null || count == null) {
|
|
23
|
+
console.warn(`Dev-Toolbar: Für das Layout "${layout}" fehlen Angaben in der config.json (layouts, margins, columns, gutter). Wird initDev die Config übergeben?`);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Die Spaltenbreite ist `zaehler / count` und oft kein ganzer Designpixel —
|
|
28
|
+
// in `template-kirbycms` etwa 91,428…. Deshalb wird sie nie ausgerechnet:
|
|
29
|
+
// Das Verhältnis Spalte zu Gutter ist `zaehler/count : gutter`, und mit
|
|
30
|
+
// `count` erweitert wird daraus `zaehler : count · gutter`, also ein
|
|
31
|
+
// Verhältnis zweier ganzer Zahlen. Gekürzt ergibt das exakt das gesuchte
|
|
32
|
+
// Muster, ohne jede Rundung.
|
|
33
|
+
const zaehler = width - margins.left - margins.right - (count - 1) * gutter;
|
|
34
|
+
|
|
35
|
+
if (!Number.isInteger(zaehler) || !Number.isInteger(gutter) || !Number.isInteger(count) || zaehler <= 0) {
|
|
36
|
+
console.warn(`Dev-Toolbar: Layout "${layout}" ergibt keine brauchbare Spaltenaufteilung (Rest ${zaehler}, Gutter ${gutter}, Spalten ${count}) — das Raster bleibt aus.`);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const unit = greatestCommonDivisor(zaehler, count * gutter);
|
|
41
|
+
const band = zaehler / unit;
|
|
42
|
+
const gap = (count * gutter) / unit;
|
|
43
|
+
|
|
44
|
+
return [count * band + (count - 1) * gap, band, gap];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function greatestCommonDivisor(a, b) {
|
|
48
|
+
return b === 0 ? a : greatestCommonDivisor(b, a % b);
|
|
49
|
+
}
|
package/dev/toolbar/toolbar.scss
CHANGED
|
@@ -7,69 +7,43 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// grid
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
$gutter: size($layout, $gutter);
|
|
17
|
-
$margin-left: size($layout, $margin-left);
|
|
18
|
-
$margin-right: size($layout, $margin-right);
|
|
19
|
-
|
|
20
|
-
.dev-toolbar__grid::after {
|
|
21
|
-
// the grid is made through repition
|
|
22
|
-
// 'important' because background settings are more specific than the grid settings
|
|
23
|
-
background-size: calc((100% - ($columns * $gutter)) * calc(1 / $columns) + $gutter) 1px !important;
|
|
24
|
-
margin: 0 calc($margin-right - calc($gutter / 2)) 0 calc($margin-left - calc($gutter / 2));
|
|
25
|
-
// The grid is calculated starting from the middle
|
|
26
|
-
// Thats why its necessary to add the half of the column gap to the left and right side
|
|
27
|
-
width: calc(100% - $margin-right + calc($gutter / 2) - $margin-left + calc($gutter / 2));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
body[data-dev-grid="lines"] {
|
|
31
|
-
.dev-toolbar__grid::after {
|
|
32
|
-
background:
|
|
33
|
-
// center of the column spacing
|
|
34
|
-
linear-gradient(90deg, transparent 0.5px, transparent 0.5px) 0 0,
|
|
35
|
-
// Left column line
|
|
36
|
-
// position: from the column spacing center to the right by half a column spacing
|
|
37
|
-
linear-gradient(90deg, $color--dev-grid 0.5px, transparent 0.5px) calc($gutter / 2) 0,
|
|
38
|
-
// right column line
|
|
39
|
-
linear-gradient(90deg, $color--dev-grid 0.5px, transparent 0.5px) calc($gutter / 2 * -1) 0;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
body[data-dev-grid="ribbons"] {
|
|
44
|
-
.dev-toolbar__grid::after {
|
|
45
|
-
background:
|
|
46
|
-
// ceft column ribbon
|
|
47
|
-
// position: from the column spacing center to the right by half a column spacing
|
|
48
|
-
linear-gradient(90deg, $color--dev-grid-ribbons calc(100% - $gutter), transparent 0.5px) calc($gutter / 2) 0,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
10
|
+
//
|
|
11
|
+
// Gezeichnet wird das Raster von `canvas-grid-lines`; Toolbar.js hängt das
|
|
12
|
+
// Canvas in dieses Element. Hier steht nur noch, wo das Element liegt.
|
|
13
|
+
// Die Linienfarben stehen als GRID_MODES in Toolbar.js — ein Canvas nimmt sie
|
|
14
|
+
// zur Laufzeit entgegen, nicht als Sass-Variable.
|
|
53
15
|
.dev-toolbar__grid {
|
|
54
|
-
|
|
55
|
-
|
|
16
|
+
// Spannt genau die Rasterbreite auf: Seitenränder aus `$margins`, Breite
|
|
17
|
+
// aus `left`/`right`. Die frühere Verschiebung um eine halbe Gutter-Breite
|
|
18
|
+
// entfällt — sie richtete nur die Kachelwiederholung des Gradienten aus.
|
|
56
19
|
height: 100%;
|
|
57
|
-
left: 0;
|
|
58
20
|
pointer-events: none;
|
|
59
21
|
position: fixed;
|
|
60
22
|
top: 0;
|
|
61
23
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
24
|
+
// Nicht `display: none`: Ein Container ohne Ausdehnung schickt
|
|
25
|
+
// canvas-grid-lines auf seinen Lazy-Pfad, das Raster bliebe ungezeichnet.
|
|
26
|
+
visibility: hidden;
|
|
27
|
+
|
|
28
|
+
// Immer vor dem Inhalt: Ein Messraster, das hinter dem liegt, was man
|
|
29
|
+
// vermessen will, taugt nicht — früher galt das nur für `lines`, `ribbons`
|
|
30
|
+
// lag dahinter. Der z-index macht zugleich den Stacking Context auf, ohne
|
|
31
|
+
// den das Canvas hinter den Hintergrund des Body rutschte.
|
|
32
|
+
//
|
|
33
|
+
// Gleicher Wert wie `.lil-gui.lil-root`, und das ist Absicht: Bei
|
|
34
|
+
// Gleichstand entscheidet die Dokumentreihenfolge, und die Toolbar hängt
|
|
35
|
+
// sich nach dem Overlay ein. Das Panel bleibt also bedienbar.
|
|
36
|
+
z-index: 9999999;
|
|
65
37
|
|
|
66
|
-
body[data-dev-grid='
|
|
67
|
-
|
|
38
|
+
body:not([data-dev-grid='aus']) & {
|
|
39
|
+
visibility: visible;
|
|
68
40
|
}
|
|
69
41
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
// Bewusst statt eines Imports von `canvas-grid-lines/css`: Dessen Regel
|
|
43
|
+
// greift über `[data-grid-initialised] canvas` auf jedes Canvas im Dokument
|
|
44
|
+
// und würde die Rasterebenen im Projekt mit `z-index: -1` nach hinten
|
|
45
|
+
// ziehen. Hier bleibt sie auf das Overlay beschränkt.
|
|
46
|
+
canvas {
|
|
73
47
|
left: 0;
|
|
74
48
|
position: absolute;
|
|
75
49
|
top: 0;
|
|
@@ -78,7 +52,10 @@ $color--dev-grid-center: rgba(0, 0, 0, 0.2);
|
|
|
78
52
|
|
|
79
53
|
@each $layout, $breite in $layouts {
|
|
80
54
|
@include mediaquery($layout) using ($layout) {
|
|
81
|
-
|
|
55
|
+
.dev-toolbar__grid {
|
|
56
|
+
left: size($layout, map.get(map.get($margins, $layout), 'left'));
|
|
57
|
+
right: size($layout, map.get(map.get($margins, $layout), 'right'));
|
|
58
|
+
}
|
|
82
59
|
}
|
|
83
60
|
}
|
|
84
61
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@profitlich/template-toolkit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Shared SCSS layout system, JS utilities, components and build scripts for profitlich template repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sass": "scss/forward.scss",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"basic-ftp": "^5.0.0",
|
|
42
|
+
"canvas-grid-lines": "^10.3.0",
|
|
42
43
|
"chokidar": "^4.0.0",
|
|
43
44
|
"cli-progress": "^3.12.0",
|
|
44
45
|
"dotenv": "^17.0.0",
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
},
|
|
65
66
|
"repository": {
|
|
66
67
|
"type": "git",
|
|
67
|
-
"url": "git+https://github.com/profitlich-ch/
|
|
68
|
+
"url": "git+https://github.com/profitlich-ch/template-toolkit.git"
|
|
68
69
|
},
|
|
69
70
|
"license": "MIT"
|
|
70
71
|
}
|
package/dev/toolbar/COLUMNS.md
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
# Plan: Exakte Spaltenlinien via Canvas (Variante E)
|
|
2
|
-
|
|
3
|
-
## Context
|
|
4
|
-
|
|
5
|
-
Im `lines`-Modus des Dev-Grids werden Spaltenlinien über `linear-gradient` + `background-size` als Kachelmuster erzeugt. Die Kachelbreite löst auf Bruchpixel-Werte auf → die `0.5px`-Gradient-Stops landen an leicht unterschiedlichen physischen Pixeln pro Kachel. Ergebnis: Linien erscheinen wackelnd oder inkonsistent.
|
|
6
|
-
|
|
7
|
-
**Lösung:** `<canvas>`-Element mit korrekter `devicePixelRatio`-Behandlung und dem "Half-Pixel"-Trick für pixelgenaue, scharf gerenderte Linien. Funktioniert auf 1×- und Retina-Displays gleich gut.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Betroffene Dateien
|
|
12
|
-
|
|
13
|
-
- [dev/toolbar/Toolbar.js](dev/toolbar/Toolbar.js)
|
|
14
|
-
- [dev/toolbar/toolbar.scss](dev/toolbar/toolbar.scss)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Architektur
|
|
19
|
-
|
|
20
|
-
```text
|
|
21
|
-
.dev-toolbar__grid (position: fixed, 100vw×100vh)
|
|
22
|
-
└── <canvas> ← neu, ersetzt ::after für "lines"
|
|
23
|
-
::after ← bleibt für "ribbons" erhalten
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Das Canvas-Element wird als Kind von `#gridElement` angehängt. Es erbt dessen Fixed-Positioning und Visibility-Steuerung über `data-dev`. Für den `lines`-Modus zeichnet das Canvas; für `ribbons` wird das Canvas geleert und `::after` übernimmt.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## SCSS — `toolbar.scss`
|
|
31
|
-
|
|
32
|
-
### 1. CSS Custom Properties im Mixin ergänzen
|
|
33
|
-
|
|
34
|
-
Im Block `body[data-dev='true']` innerhalb von `dev-toolbar-grid`:
|
|
35
|
-
|
|
36
|
-
```scss
|
|
37
|
-
body[data-dev='true'] {
|
|
38
|
-
--dev-columns: #{$columns}; // unitless integer
|
|
39
|
-
--dev-gutter: #{$gutter}; // CSS-Wert (z. B. "1.5rem" oder "20px")
|
|
40
|
-
--dev-margin-left: #{$margin-left};
|
|
41
|
-
--dev-margin-right: #{$margin-right};
|
|
42
|
-
|
|
43
|
-
.dev-toolbar__grid::after { ... } // unverändert
|
|
44
|
-
}
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 2. `lines`-Gradient entfernen
|
|
48
|
-
|
|
49
|
-
Den gesamten Block (Zeilen 28–39) entfernen — das Canvas übernimmt die Darstellung:
|
|
50
|
-
|
|
51
|
-
```scss
|
|
52
|
-
// ENTFERNEN:
|
|
53
|
-
body[data-dev='true'][data-dev-grid="lines"] {
|
|
54
|
-
.dev-toolbar__grid::after { background: ...; }
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 3. Canvas-Styling ergänzen
|
|
59
|
-
|
|
60
|
-
```scss
|
|
61
|
-
.dev-toolbar__grid {
|
|
62
|
-
canvas {
|
|
63
|
-
display: block;
|
|
64
|
-
width: 100%;
|
|
65
|
-
height: 100%;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## JavaScript — `Toolbar.js`
|
|
73
|
-
|
|
74
|
-
### Neue private Fields
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
#gridElement = null; // bisher nur lokale Variable, jetzt gespeichert
|
|
78
|
-
#canvas = null;
|
|
79
|
-
#ctx = null;
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### `constructor()` — Änderungen
|
|
83
|
-
|
|
84
|
-
1. `gridOverlay` → `this.#gridElement` (Referenz speichern)
|
|
85
|
-
2. `#initCanvas()` aufrufen nach `prepend`
|
|
86
|
-
|
|
87
|
-
```javascript
|
|
88
|
-
this.#gridElement = document.createElement('div');
|
|
89
|
-
this.#gridElement.classList.add('dev-toolbar__grid');
|
|
90
|
-
document.body.prepend(this.#gridElement);
|
|
91
|
-
this.#initCanvas();
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Neue Methode `#initCanvas()`
|
|
95
|
-
|
|
96
|
-
```javascript
|
|
97
|
-
#initCanvas() {
|
|
98
|
-
this.#canvas = document.createElement('canvas');
|
|
99
|
-
this.#gridElement.appendChild(this.#canvas);
|
|
100
|
-
this.#ctx = this.#canvas.getContext('2d');
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Neue Methode `#drawGrid()`
|
|
105
|
-
|
|
106
|
-
Wird aufgerufen bei: State-Wechsel (`#applyState`), Resize (`#onResize`).
|
|
107
|
-
|
|
108
|
-
```javascript
|
|
109
|
-
#drawGrid() {
|
|
110
|
-
const dpr = window.devicePixelRatio || 1;
|
|
111
|
-
const w = window.innerWidth;
|
|
112
|
-
const h = window.innerHeight;
|
|
113
|
-
|
|
114
|
-
this.#canvas.width = Math.round(w * dpr);
|
|
115
|
-
this.#canvas.height = Math.round(h * dpr);
|
|
116
|
-
|
|
117
|
-
this.#ctx.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
|
|
118
|
-
|
|
119
|
-
if (this.#state.grid !== 'lines') return;
|
|
120
|
-
|
|
121
|
-
this.#ctx.setTransform(dpr, 0, 0, dpr, 0.5, 0); // scale + half-pixel shift
|
|
122
|
-
|
|
123
|
-
this.#drawColumnLines();
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
**Half-pixel-Trick:** `ctx.setTransform(dpr, 0, 0, dpr, 0.5, 0)` — der 0.5px-Shift zentriert 1px-Linien exakt auf physische Pixel bei dpr=1. Bei dpr=2 (Retina) ist 0.5 CSS-px = 1 physischer Pixel, ebenfalls scharf.
|
|
128
|
-
|
|
129
|
-
### Neue Methode `#drawColumnLines()`
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
#drawColumnLines() {
|
|
133
|
-
const style = getComputedStyle(document.body);
|
|
134
|
-
const columns = parseInt(style.getPropertyValue('--dev-columns'));
|
|
135
|
-
const gutter = this.#resolveToPx(style.getPropertyValue('--dev-gutter').trim());
|
|
136
|
-
const marginLeft = this.#resolveToPx(style.getPropertyValue('--dev-margin-left').trim());
|
|
137
|
-
|
|
138
|
-
// Kachelbreite = (Viewport - Margins + Gutter) / Spalten
|
|
139
|
-
// (identisch zur background-size-Formel im SCSS)
|
|
140
|
-
const gridWidth = window.innerWidth - marginLeft
|
|
141
|
-
- this.#resolveToPx(style.getPropertyValue('--dev-margin-right').trim())
|
|
142
|
-
+ gutter;
|
|
143
|
-
const tileWidth = gridWidth / columns;
|
|
144
|
-
const colContent = tileWidth - gutter;
|
|
145
|
-
|
|
146
|
-
this.#ctx.strokeStyle = 'rgba(0, 0, 255, 0.5)';
|
|
147
|
-
this.#ctx.lineWidth = 1;
|
|
148
|
-
const h = window.innerHeight;
|
|
149
|
-
|
|
150
|
-
for (let i = 0; i < columns; i++) {
|
|
151
|
-
const leftEdge = marginLeft + i * tileWidth;
|
|
152
|
-
const rightEdge = leftEdge + colContent;
|
|
153
|
-
|
|
154
|
-
this.#ctx.beginPath();
|
|
155
|
-
this.#ctx.moveTo(Math.round(leftEdge), 0);
|
|
156
|
-
this.#ctx.lineTo(Math.round(leftEdge), h);
|
|
157
|
-
this.#ctx.stroke();
|
|
158
|
-
|
|
159
|
-
this.#ctx.beginPath();
|
|
160
|
-
this.#ctx.moveTo(Math.round(rightEdge), 0);
|
|
161
|
-
this.#ctx.lineTo(Math.round(rightEdge), h);
|
|
162
|
-
this.#ctx.stroke();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Neue Methode `#resolveToPx(cssValue)`
|
|
168
|
-
|
|
169
|
-
Löst beliebige CSS-Einheiten (rem, vw, calc, …) auf px auf, ohne Unit-Parsing-Logik:
|
|
170
|
-
|
|
171
|
-
```javascript
|
|
172
|
-
#resolveToPx(cssValue) {
|
|
173
|
-
if (/^-?\d+(\.\d+)?px$/.test(cssValue)) return parseFloat(cssValue);
|
|
174
|
-
|
|
175
|
-
const probe = document.createElement('div');
|
|
176
|
-
probe.style.cssText = `position:fixed;visibility:hidden;width:${cssValue};top:0;left:0;`;
|
|
177
|
-
document.body.appendChild(probe);
|
|
178
|
-
const px = probe.getBoundingClientRect().width;
|
|
179
|
-
probe.remove();
|
|
180
|
-
return px;
|
|
181
|
-
}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### `#applyState()` — Ergänzung
|
|
185
|
-
|
|
186
|
-
```javascript
|
|
187
|
-
#applyState() {
|
|
188
|
-
const gridActive = this.#state.grid !== 'aus';
|
|
189
|
-
document.body.setAttribute('data-dev', String(gridActive));
|
|
190
|
-
document.body.setAttribute('data-dev-grid', this.#state.grid);
|
|
191
|
-
this.#updateImageSize();
|
|
192
|
-
this.#drawGrid(); // ← neu
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### `#onResize` — Ergänzung
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
#onResize = () => {
|
|
200
|
-
this.#gui.title(this.#getViewportText());
|
|
201
|
-
this.#updateImageSize();
|
|
202
|
-
this.#drawGrid(); // ← neu
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Positionsformel (Herleitung)
|
|
209
|
-
|
|
210
|
-
Das SCSS positioniert `::after` mit `margin-left = marginLeft - gutter/2`, sodass die Kacheln mittig zwischen Spalten beginnen. Die resultierenden Canvas-Positionen sind:
|
|
211
|
-
|
|
212
|
-
| | Formel | Beispiel (12 Spalten, gutter=20px, marginLeft=40px) |
|
|
213
|
-
| --- | --- | --- |
|
|
214
|
-
| Linke Kante Spalte i | `marginLeft + i × tileWidth` | i=0: 40px, i=1: 120px, … |
|
|
215
|
-
| Rechte Kante Spalte i | `marginLeft + i × tileWidth + colContent` | i=0: 100px, i=1: 180px, … |
|
|
216
|
-
|
|
217
|
-
Diese Formel ist identisch zur bestehenden `background-size`-Logik, nur ohne Kachel-Grenz-Problem.
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Verifikation
|
|
222
|
-
|
|
223
|
-
1. `ddev npm run dev` starten
|
|
224
|
-
2. Dev-Toolbar öffnen (Ctrl), Grid auf "lines" schalten
|
|
225
|
-
3. **Browserfenster in verschiedene Breiten ziehen** → Linien dürfen nicht wackeln
|
|
226
|
-
4. **Safari (Retina) + Chrome (1×)** vergleichen → gleiche Schärfe
|
|
227
|
-
5. **Ribbons-Modus** prüfen → Canvas geleert, `::after`-Gradient unverändert
|
|
228
|
-
6. **Breakpoint-Wechsel** prüfen → CSS Custom Properties werden neu gelesen, Linienanzahl passt sich an
|