如何在单词选择游戏中绘制橡皮筋或套索进行选择
Posted
技术标签:
【中文标题】如何在单词选择游戏中绘制橡皮筋或套索进行选择【英文标题】:How to draw a rubberband or lasso to make a selection in word selection game 【发布时间】:2022-01-04 21:59:08 【问题描述】:我正在尝试在我的网页上放置一个单词搜索游戏。 我看到这里是一个漂亮的例子:Embed a Word Search in Your Site
我想做类似的gui,这样当用户通过鼠标拖动从网格中选择单词字母时,它会在所选字母周围画一个红色的弹性橡皮筋。
他们如何在他们的网页上做到这一点?我试图在绘制乐队后“检查”html,但没有看到任何添加的元素。
谁能给我点线索?
【问题讨论】:
【参考方案1】:这是一个 JS 示例
class App
constructor(div)
this.div = div;
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext('2d');
this.dirty = true;
this.prev = +new Date();
this.resize();
window.addEventListener('resize', () => this.resize(), false);
this.canvas.addEventListener("mousedown", (event) =>
this.touchdown(...App.getmousePos(event));
event.preventDefault();
);
this.canvas.addEventListener("mousemove", (event) =>
this.touchmove(...App.getmousePos(event));
event.preventDefault();
);
this.canvas.addEventListener("mouseup", (event) =>
this.touchup(...App.getmousePos(event));
event.preventDefault();
);
this.canvas.addEventListener("touchstart", (event) =>
this.touchdown(...App.getmousePos(event));
event.preventDefault();
);
this.canvas.addEventListener("touchmove", (event) =>
this.touchmove(App.getmousePos(event));
event.preventDefault();
);
this.canvas.addEventListener("touchend", (event) =>
this.touchup(App.getmousePos(event));
event.preventDefault();
);
resize()
this.canvas.width = this.div.clientWidth;
this.canvas.height = this.div.clientWidth;
this.draw();
loop()
let now = +new Date();
let dt = now - this.prev;
this.prev = now;
this.update()
if (this.dirty)
this.draw();
window.requestAnimationFrame(() => this.loop());
update(dt)
draw()
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.rect(0, 0, 100, 100);
this.ctx.fill();
touchdown(x, y) console.log("down", x, y);
touchmove(x, y)
touchup(x, y)
static getmousePos(event)
if (event.changedTouches)
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
else
var rect = event.target.getBoundingClientRect();
return [event.clientX- rect.left, event.clientY - rect.top];
class Vector
constructor(x, y)
this.x = x;
this.y = y;
add(v_x, y)
if (y != undefined)
return new Vector(this.x + v_x, this.y + y);
else
return new Vector(this.x + v_x.x, this.y + v_x.y);
sub(v_x, y)
if (y != undefined)
return new Vector(this.x - v_x, this.y - y);
else
return new Vector(this.x - v_x.x, this.y - v_x.y);
mul(s)
return new Vector(this.x * s, this.y * s);
get slength()
return this.x * this.x + this.y * this.y;
get length()
return Math.sqrt(this.x * this.x + this.y * this.y);
get angle()
return Math.atan2(this.y, this.x);
class Grid
constructor(width, height, data)
this.width = width;
this.height = height;
this.data = data || new Array(width*height).fill("_");
get length()
return this.width*this.height;
get(x_pos, y)
if (y != undefined)
return this.data[x_pos+y*this.width];
else
return this.data[x_pos];
set(value, x_pos, y)
if (y != undefined)
this.data[x_pos+y*this.width] = value;
else
this.data[x_pos] = value;
clone()
return new Grid(this.width, this.height, this.data.slice());
finalize()
this.data = this.data.map(v => v == "_" ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) : v);
print()
for (let i = 0; i < this.height; i++)
console.log(this.data.slice(i*this.width, (i+1)*this.width).join(","));
class Puzzle
constructor(width, height, words, directions)
this.grid = new Grid(width, height);
this.words = words;
this.placeWords(words, directions);
placeWords(words, directions)
const range = (N) => Array.from(length: N, (v, k) => k);
const shuffle = (array) =>
for (let i = array.length - 1; i > 0; i--)
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
return array;
words = words.slice();
let positions = range(this.grid.length);
let stack = [
grid: this.grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()) ];
while (true)
let current = stack[stack.length-1];
if (!current)
throw Error("impossible");
let dir = current.directions.pop();
if (!dir)
current.positions.pop();
current.directions = shuffle(directions.slice());
dir = current.directions.pop();
if (current.positions.length <= 0)
words.unshift(current.word);
stack.pop();
else
let pos = current.positions[current.positions.length-1];
let grid = this.placeWord(current.grid, current.word, pos, dir);
if (grid)
if (words.length > 0)
stack.push(grid: grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()));
else
grid.finalize();
this.grid = grid;
break;
placeWord(grid, word, position, direction)
let copy = grid.clone();
position = new Vector(position % grid.width, Math.floor(position / grid.width));
let letters = [...word];
while (0 <= position.x && position.x < grid.width && 0 <= position.y && position.y < grid.height)
if (letters.length <= 0)
break;
let letter = letters.shift();
if (copy.get(position.x, position.y) == "_" || copy.get(position.x, position.y) == letter)
copy.set(letter, position.x, position.y);
position = position.add(direction);
else
return null;
if (letters.length > 0)
return null;
else
return copy;
wordAt(position, direction, length)
let word = new Array(length);
for (let i = 0; i < length; i++)
word[i] = this.grid.get(position.x, position.y);
position = position.add(direction);
return word.join("");
print()
this.grid.print();
class Selection
constructor(position, fill=false)
this.position = position;
this._fill = fill;
this.direction = new Vector(1, 0);
this.length = 0;
this.flength = 0;
clone()
return new Selection(this.position).to(this.position.add(this.direction.mul(this.length)));
to(position)
let direction = position.sub(this.position);
if (Math.abs(direction.y) == 0)
this.direction = new Vector(direction.x >= 0 ? 1 : -1, 0);
this.length = direction.x * this.direction.x;
else if (Math.abs(direction.x) == 0)
this.direction = new Vector(0, direction.y >= 0 ? 1 : -1);
this.length = direction.y * this.direction.y;
else
this.direction = new Vector(direction.x >= 0 ? 1 : -1, direction.y >= 0 ? 1 : -1);
this.length = direction.x * this.direction.x;
this.flength = direction.length;
return this;
fill(fill=true)
this._fill = fill;
return this;
draw(ctx, wsize, hsize)
let pos = this.position.mul(wsize);
let length = wsize * (this.flength+1);
let angle = this.direction.angle;
//console.log(this.x, this.y, x, y, length, angle, this.dx, this.dy);
ctx.save();
ctx.strokeStyle = "black";
ctx.fillStyle = "yellow";
ctx.translate(pos.x+hsize*0.5, pos.y+hsize*0.5);
ctx.rotate(angle);
this.drawHighlight(ctx, -hsize*0.5, -hsize*0.5, length, hsize, hsize*0.5, this._fill, true);
ctx.restore();
drawHighlight(ctx, x, y, width, height, radius=20, fill=true, stroke=false)
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
class PuzzleApp extends App
constructor(div, puzzle)
super(div);
this.puzzle = puzzle;
this.renderList(document.getElementById("list"));
this.selections = new Array();
this.loop();
renderList(parent)
this.puzzle.words.forEach(word =>
let li = document.createElement("li");
let text = document.createTextNode(word);
li.appendChild(text);
parent.appendChild(li);
);
gridSize()
let wsize = Math.floor(this.canvas.width/this.puzzle.grid.width);
let hsize = Math.floor(this.canvas.width/this.puzzle.grid.height);
return [wsize, hsize];
clientToGrid(x, y)
let [wsize, hsize] = this.gridSize();
x = Math.floor(x / wsize);
y = Math.floor(y / hsize);
return [x, y];
draw()
if (!this.puzzle)
return;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
let [wsize, hsize] = this.gridSize();
this.selections.forEach(s => s.draw(this.ctx, wsize, hsize));
if (this.selection)
this.selection.draw(this.ctx, wsize, hsize);
let x = 0;
let y = 0;
this.ctx.fillStyle = "black";
this.ctx.font = (wsize * 0.5) + 'px sans-serif';
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
for (let j = 0; j < this.puzzle.grid.height; j++)
for (let i = 0; i < this.puzzle.grid.width; i++)
let letter = this.puzzle.grid.get(i, j);
this.ctx.fillText(letter, x+wsize * 0.5, y+wsize * 0.5);
x += wsize;
x = 0;
y += wsize;
touchdown(x, y)
[x, y] = this.clientToGrid(x, y);
this.selection = new Selection(new Vector(x, y));
this.dirty = true;
touchmove(x, y)
if (!this.selection)
return;
[x, y] = this.clientToGrid(x, y);
this.selection.to(new Vector(x, y));
touchup(x, y)
if (!this.selection)
return;
let word = this.puzzle.wordAt(this.selection.position, this.selection.direction, this.selection.length+1);
console.log(word);
if (word)
let list = document.getElementById("list");
let elements = list.getElementsByTagName("li");
Array.prototype.some.call(elements, li =>
if (li.innerText == word)
li.classList.add("found");
this.selections.push(this.selection.clone().fill());
return true;
return false;
);
this.selection = null;
let app = null;
document.addEventListener("DOMContentLoaded", function(event)
const wordLists = [
["pear", "apple", "banana", "peach", "kiwi", "prune", "persimon"]
]
const pick = (array) => array[Math.floor(Math.random() * (array.length))];
let params = (new URL(document.location)).searchParams;
let directions = [new Vector(1,0), new Vector(0,1)];
if (params.get("diagonal"))
directions.push(new Vector(1,1));
if (params.get("backwards"))
directions.push(new Vector(-1,0));
directions.push(new Vector(0,-1));
if (params.get("diagonal"))
directions.push(new Vector(-1,-1));
let puzzle = new Puzzle(8, 8, pick(wordLists), directions);
puzzle.print();
app = new PuzzleApp(document.getElementById("canvasContainer"), puzzle);
);
html, body
width: 100%;
height: 100%;
margin: 0px;
ul
display: inline-block;
font-size: 7vmin;
padding-left: 14vmin;
li.found
text-decoration: line-through;
div#canvasContainer
width: 100%;
display: inline-block;
@media (min-aspect-ratio: 1/1)
ul
font-size: 4vmin;
padding-left: 7vmin;
div#canvasContainer
width: 50%;
<div id="canvasContainer"><canvas id="canvas" ></canvas></div>
<ul id="list"></ul>
我会让你弄清楚如何改变橡皮筋的粗细和颜色。
【讨论】:
【参考方案2】:红色带是 svg。单击时不会添加它,而是会重新绘制它。
【讨论】:
【参考方案3】:我检查了引用的网站,似乎他们在 SVG 中绘制了橡皮筋。如果您在绘图时检查此 SVG 标记:
您会注意到它显示了一些更新。
您可以使用相同的 SVG 方法。或者另一种方法是使用绝对定位的 div 并根据 mousedown 更新顶部、左侧 css 值,根据 mousemove 事件更新宽度。
【讨论】:
【参考方案4】:我看到他们做了什么。
他们创建了一个 svg 元素。这个 svg 正好位于上面并覆盖了包含字母网格的整个表格。当用户单击任何字母并开始拖动时,他们会在 svg 中添加一个矩形。根据光标的移动方式,它们对矩形进行角度变换。如果正确选择了单词,则矩形仍然存在;否则,它会被删除。
我现在需要找出其中的“如何”部分。
感谢大家的快速回复。
【讨论】:
以上是关于如何在单词选择游戏中绘制橡皮筋或套索进行选择的主要内容,如果未能解决你的问题,请参考以下文章
如何在 IntelliJ IDEA 中选择骆驼化单词的单个单词