-
Notifications
You must be signed in to change notification settings - Fork 0
/
thletter1.html
323 lines (294 loc) · 8.74 KB
/
thletter1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
<!-- Author: Sunthorn Rathmnus
released on GPL v3 14 April 2024
-->
<!DOCTYPE HTML>
<html lang = "th-TH">
<head>
<meta charset = "utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>คัด ก ข</title>
<style>
#board {
width: 80%; height: 25%;
margin: auto;
background-color: #ffffe6;
border: 1px solid black;
}
#plot path {
fill: transparent;
stroke: lightgrey;
stroke-width: 20;
stroke-dashoffset: 3000;
}
#board:hover path {
stroke: indigo;
stroke-dasharray: 3000;
stroke-dashoffset: 0;
transition: stroke-dashoffset 6s linear;
}
#list {
width: 95%;
margin: auto;
}
.card {
width: 50px; height: 56px;
background-color: yellow;
border: 1px solid lime;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 48px;
line-height: 1.1;
vertical-align: middle;
margin: 4px 2px;
cursor: pointer;
border-radius: 15%;
}
.card:hover {
animation: shake 0.5s;
animation-iteration-count: 2;
}
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
.hidden {
display: none;
}
</style>
</head>
<body onload="checkDevice">
<h1 style="text-align:center;">คัด ก ข</h1>
<div id="board" >
<svg id="plot" viewBox="0 100 1000 800"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<line x1="300" y1="260" x2="700" y2="260" style="stroke:grey; stroke-width:1" />
<line x1="300" y1="750" x2="700" y2="750" style="stroke:grey; stroke-width:1" />
<path id="letter" d="M 0,100" />
<circle r="16" fill="red">
<animate
attributeName="fill"
attributeType="XML"
values="green;lime;orange;red"
keyTimes= "0; 0.4; 0.7; 1"
dur="8s"
repeatCount="indefinite"
/>
<animateMotion
path="M 0,100"
dur="8s"
repeatCount="indefinite"
/>
</circle>
<path id="trace" d="M0,800" />
<text id="score" x="750" y="750" fill="green" font-size="60"> </text>
</svg>
</div>
<div id="list" />
<script src="script/กขคpaths.js"></script>
<script>
// declare some global variables and handles
let board = document.getElementById("board");
let plot = document.getElementById("plot"); // svg area
let path = document.querySelector("path");
let pen = document.querySelector("animateMotion");
let list = document.getElementById("list"); // list of letters
let letter = document.getElementById("letter");
let trace = document.getElementById("trace");
let score = document.getElementById("score");
var pathdata = "",
nMs; // M's in pathdata -> multipaths, assumed 1 M
// **handle drawing of the letter by dragging on screen
var track = "", // waypoints x,y of path drawn
nsegs, // line segments drawn
tracking = false;
var taps =0; //for debugging detapper
let aTouch = false; // assume a click device
function checkDevice () {
let tested = false;
if (!tested) {
document.createEventListener("touchStart", () => {aTouch = true});
tested = true;
}
}
//stop/restart animation with a click/touch on the board
function redraw() {
plot.setCurrentTime(0);
}
// make the board the restart button
if (aTouch)
board.addEventListener("touchStart", redraw);
else
board.addEventListener("click", redraw);
// get keys of letterpaths
let letterpaths = กขค;
let letters = Object.keys(letterpaths);
// set up display a list of letters
for ( let x of letters) {
var a = document.createElement("span"); //create a card as a span element with classname "card"
a.className = "card";
a.innerText = x;
list.appendChild(a); // add to list
}
// add event click or touchStart to list
if (!aTouch) {
list.addEventListener("click", function(e) {
e = e || list.event; // other events give errors
var t = e.target || e.srcElement;
InitLetter(e.target);
}, false);
} else {
list.addEventListener("touchStart", function(e) {
e = e || list.event; // other events give errors
var t = e.target || e.srcElement;
InitLetter(e.target);
}, false);
}
//create invisible audio element
const snd = document.createElement("audio");
function say(x){
var sfx = "audio/v"+ x + ".mp3";
snd.setAttribute("src", sfx);
snd.play();
snd.currentTime=0;
}
function InitLetter(e) {
// a letter selected
pathdata = letterpaths[e.innerText]; //by object key
nMs = pathdata.split("M").length - 1; // how many M commands
nsegs = 0;
track = "";
tracking = false;
taps = 0;
score.classList.add("hidden"); // hide the score
// inject pathdata into svg elements
path.setAttribute("d", pathdata);
pen.setAttribute("path", pathdata);
trace.setAttribute("d", track);
redraw();
// change border of the selected card
e.style.border = "1px dashed green";
e.style.backgroundColor = "white";
// audio the letter
say(e.innerText);
}
// setup to capture 'drag' and display its path
if (aTouch) {
plot.addEventListener("touchEnd", function () {
detapper();
});
plot.addEventListener("touchMove", function(e) {
e.preventDefault();
if (tracking) ondrag(e);
});
} else {
plot.addEventListener("mouseup", function () {
detapper();
});
plot.addEventListener("mousemove", function(e) {
if (tracking) ondrag(e);
});
}
function detapper () {
// decode the sequence of taps (click|touch) to actions
taps +=1; console.log("times taps:"+taps); // for debugging
if (!tracking) { // turn on tracking
tracking = true;
if (!nsegs || nsegs == nMs) track = "";
showmark("✏️");
} else { // end tracking, asses track
if (track) {
nsegs = track.split("M").length - 1;
if (nsegs < nMs) { // not trace all paths
track += " M";
tracking = false; // force to tap again to
} else {
assess();
tracking = false;
}
}
}
}
function ondrag(e) {
// capture waypoints every 300ms
drawtrace(e);
setTO();
}
function setTO(t=300) {
// turn tracking off until timeout t occurs
tracking = false;
setTimeout(function() { tracking = true; }, t);
}
// tracking pointer
function ptrXY(e) {
const dpt = new DOMPointReadOnly(e.clientX, e.clientY)
const pt = dpt.matrixTransform(plot.getScreenCTM().inverse())
return [Math.round(pt.x), Math.round(pt.y) ];
}
// when mousemove or touchMove
function drawtrace(e) {
var [ x, y ] = ptrXY(e);
if (track=="") {
track += " M"+x+","+y;
}
else {
track += " "+x+","+y;
}
// redraw to look real
trace.style.stroke="red";
trace.style.strokeWidth = "10";
trace.setAttribute("d", track);
}
function showmark(x) {
score.textContent=x;
score.classList.remove("hidden");
}
function assess() {
// asses dragged track against letter path
// pick sample points from length 10,step 50 while length<getPointAtLength(path)
// compare subtract and square and sum then divide by # sample points
// give mark by value within range (...)
let bb1 = letter.getBBox();
let bb2 = trace.getBBox();
//console.log("BBox1 " + bb1.width +" x "+ bb1.height);
//console.log(" 2 " + bb2.width +" x "+ bb2.height);
let pl1 = letter.getTotalLength(), pl2 = trace.getTotalLength();
//console.log("TPL " + pl1 +" =? "+ pl2);
let pts1 = [], pts2 = [];
let da = 0, sumda = 0, avgda = 0, npts = 0, minda = 10000, Maxda = 0, maxp;
for (let l = 10; l<pl1; l+=50) {
let p1 = letter.getPointAtLength(l),
p2 = trace.getPointAtLength(l);
da = Math.abs((p1.x - p2.x)*(p1.y - p2.y)); // difference as an area
sumda += da; // sum total diff area
if (da < minda) minda = da; // find minimum
if (da > Maxda) {Maxda = da; maxp = p2;} // find Maximum, save the point
npts++;
pts1.push([Math.round(p1.x),Math.round(p1.y)]); // for debug
pts2.push([Math.round(p2.x),Math.round(p2.y)]);
}
//console.log("pts1 " + pts1);
//console.log("pts2 " + pts2);
if (npts) avgda = sumda/npts;
//console.log("min:"+minda+" Max:"+Maxda+ " at:("+maxp.x+","+maxp.y+") avg:"+avgda+" sum:"+sumda);
let dif = 100*sumda/(bb1.width * bb1.height);
// assess the draw by dif%
if (dif<=2.0) showmark("♥️"); // very good
else if (dif<=10.0) showmark("✔️"); // good
else if (dif<=30.0) showmark("🐞️"); // OK but
else showmark("❓️"); // more practice
console.log("Your mark:"+dif+"% pts: "+npts);
}
</script>
</div>
</body>
</html>