Почему метод .append() не добавляет полностью идентичные элементы в DOM-дерево
Всем привет! Практикуясь с ванильным JS (игра "Найди пару") столкнулся с проблемой: в DOM-дерево не добавляются одинаковые элементы li из массива tileList через .append() (точнее, добавляется лишь один элемент, а второй не появляется как узел).
const createTileList = tilesQuantity => {
const tileList = [];
for (let i = 0; i < tilesQuantity / 2; i++) {
const tile = document.createElement('button');
// ...
tileList.push(tile);
}
return tileList.flatMap(i => [i, i]).sort(() => Math.random() - 0.5);
};
// ...
const createBoard = tileList => {
const board = document.createElement('ul');
board.style.gridGap = '10px';
board.style.height = '500px';
board.style.width = '100%';
board.style.padding = '20px';
board.style.display = 'grid';
board.style.gridTemplateColumns = `repeat(${Math.sqrt(amountOfTiles)}, 1fr)`;
board.style.gridTemplateRows = `repeat(${Math.sqrt(amountOfTiles)}, 1fr)`;
board.style.borderRadius = '20px';
board.style.boxShadow = 'inset 0 0 20px #808080';
board.style.listStyle = 'none';
for (let i in tileList) {
const li = document.createElement('li');
li.append(tileList[i]);
board.append(li);
}
return board;
};
Сразу отмечу, я осознаю, что добавление стилей лучше производить в файлах-стилей, просто таким образом пытаюсь закрепить как можно больше знаний в языке.
Ответы (2 шт):
Ошибка в использовании одного и того-же элемента для одинаковых карточек, в результате чего повторное добавление элемента новому родителю "отвязывает" его от предыдущего родителя.
Дублирование у вас происходит в этом месте tileList.flatMap(i => [i, i])
const createTileList = tilesQuantity => {
const tileList = [];
for (let i = 0; i < tilesQuantity / 2; i++) {
const tile = document.createElement('button');
// ...
tileList.push(tile);
}
return tileList.flatMap(i => [i, i]).sort(() => Math.random() - 0.5);
};
На скорую руку можно исправить код клонированием элементов, вот только при клонировании обработчики событий теряются, поэтому обработчик выносим в отдельную функцию и подключаем его так-же к копии (fletmap разумеется удаляем)
(() => {
const getRandomColor = () => {
let red = Math.floor(Math.random() * 256)
let green = Math.floor(Math.random() * 256)
let blue = Math.floor(Math.random() * 256)
return `rgb(${red}, ${green}, ${blue})`
}
let amountOfTiles = 16
let choosenTile = null
let isSearching = false
let openedTiles = 0
let steps = 0
const createTileList = tilesQuantity => {
const tileList = []
for (let i = 0; i < tilesQuantity / 2; i++) {
const tile = document.createElement('button')
tile.style.height = '100%'
tile.style.width = '100%'
tile.style.borderRadius = '10px'
tile.style.backgroundColor = 'transparent'
tile.style.border = '1px solid #808080'
tile.style.transition = '0.3s'
tile.style.cursor = 'pointer'
tile.setAttribute('data-color', getRandomColor())
tile.setAttribute('data-is-opened', false)
// изменение №1: выделяем обработчик кликов
const clickHandler = e => {
let tile = e.target
if (tile.getAttribute('data-is-opened')) {
steps++
tile.style.backgroundColor = tile.getAttribute('data-color')
tile.setAttribute('data-is-opened', true)
}
setTimeout(() => {
if (isSearching) {
isSearching = false
if (tile.getAttribute('data-color') === choosenTile.getAttribute('data-color')) {
openedTiles += 2
if (openedTiles === amountOfTiles) {
alert(`You won! Quantity of steps: ${steps}. `)
}
}
else {
tile.style.backgroundColor = 'transparent'
tile.setAttribute('data-is-opened', false)
choosenTile.style.backgroundColor = 'transparent'
choosenTile.setAttribute('data-is-opened', false)
}
}
else {
choosenTile = tile
isSearching = true
}
}, 500)
}
// изменение №2: подключение обработчика к 1-му экземпляру
tile.addEventListener('click', clickHandler)
tileList.push(tile)
// изменение №3: клонируем экземпляр карточки
// (каждый клон может иметь своего родителя)
const clone = tile.cloneNode(true)
// изменение №4: подключение обработчика ко 2-му экземпляру
clone.addEventListener('click', clickHandler)
tileList.push(clone)
}
// изменение №5: удаляем старый метод удвоения карточек
// flatMap(i => [i, i]) лишь удваивал ссылки на одну карточку
return tileList.sort(() => Math.random() - 0.5)
}
const createBoard = tileList => {
const board = document.createElement('ul')
board.style.gridGap = '10px'
board.style.height = '500px'
board.style.width = '100%'
board.style.padding = '20px'
board.style.display = 'grid'
board.style.gridTemplateColumns = `repeat(${Math.sqrt(amountOfTiles)}, 1fr)`
board.style.gridTemplateRows = `repeat(${Math.sqrt(amountOfTiles)}, 1fr)`
board.style.borderRadius = '20px'
board.style.boxShadow = 'inset 0 0 20px #808080'
board.style.listStyle = 'none'
for (let i in tileList) {
const li = document.createElement('li')
li.append(tileList[i])
board.append(li)
}
return board
}
const createTileQuantitySelector = () => {
const selector = document.createElement('form')
selector.style.height = '40px'
selector.style.width = '100%'
selector.style.marginBottom = '20px'
selector.style.padding = '5px 20px'
selector.style.display = 'flex'
selector.style.justifyContent = 'space-between'
selector.style.borderRadius = '20px'
selector.style.overflow = 'hidden'
selector.style.boxShadow = 'inset 0 0 20px #808080'
const input = document.createElement('input')
input.style.display = 'none'
input.setAttribute('id', 'value-container')
input.setAttribute('type', 'number')
input.setAttribute('min', 4)
input.setAttribute('max', 100)
input.setAttribute('value', Math.sqrt(amountOfTiles))
const label = document.createElement('label')
label.style.height = '100%'
label.style.width = 'calc(50% - 5px)'
label.style.display = 'flex'
label.style.justifyContent = 'space-between'
label.style.alignItems = 'center'
label.style.borderRadius = '15px'
label.style.backgroundColor = '#FFFFFF'
label.style.boxShadow = '0 0 10px #808080'
label.setAttribute('for', 'value-selector')
const labelRightButton = document.createElement('button')
labelRightButton.style.height = '100%'
labelRightButton.style.width = '30px'
labelRightButton.style.fontSize = '20px'
labelRightButton.style.color = '#808080'
labelRightButton.style.border = 'none'
labelRightButton.style.backgroundColor = 'transparent'
labelRightButton.style.borderRadius = '50%'
labelRightButton.style.cursor = 'pointer'
labelRightButton.style.transition = '03.s'
labelRightButton.textContent = '>'
const labelLeftButton = document.createElement('button')
labelLeftButton.style.height = '100%'
labelLeftButton.style.width = '26px'
labelLeftButton.style.fontSize = '20px'
labelLeftButton.style.color = '#808080'
labelLeftButton.style.border = 'none'
labelLeftButton.style.backgroundColor = 'transparent'
labelLeftButton.style.borderRadius = '50%'
labelLeftButton.style.cursor = 'pointer'
labelLeftButton.textContent = '<'
const labelTextContent = document.createElement('div')
labelTextContent.style.fontSize = '16px'
labelTextContent.style.color = '#808080'
labelTextContent.textContent = Math.pow(input.value, 2)
label.append(labelLeftButton, labelTextContent, labelRightButton)
const button = document.createElement('button')
button.style.fontSize = '16px'
button.style.height = '100%'
button.style.width = 'calc(50% - 5px)'
button.style.padding = '5px 10px'
button.style.borderRadius = '15px'
button.style.border = 'none'
button.style.backgroundColor = '#FFFFFF'
button.style.color = '#808080'
button.style.boxShadow = '0 0 10px #808080'
button.style.cursor = 'pointer'
button.textContent = 'Set board'
selector.append(input, label, button)
return {
selector,
input,
labelRightButton,
labelLeftButton,
labelTextContent,
button
}
}
const createApp = container => {
let selector = createTileQuantitySelector()
let board = createBoard(createTileList(amountOfTiles))
container.append(selector.selector)
container.append(board)
selector.labelLeftButton.addEventListener('click', e => {
e.preventDefault()
if (Math.pow(selector.input.value, 2) <= Number(selector.input.getAttribute('min'))) {
return
}
selector.input.value -= 2
selector.labelTextContent.textContent = Math.pow(selector.input.value, 2)
})
selector.labelRightButton.addEventListener('click', e => {
e.preventDefault()
if (Math.pow(selector.input.value, 2) >= Number(selector.input.getAttribute('max'))) {
return
}
// A more pretty implementation is required
selector.input.value = +selector.input.value + Number(2)
selector.labelTextContent.textContent = Math.pow(selector.input.value, 2)
})
selector.button.addEventListener('click', e => {
e.preventDefault()
container.removeChild(board)
amountOfTiles = Math.pow(selector.input.value, 2)
board = createBoard(createTileList(amountOfTiles))
container.append(board)
})
}
document.addEventListener('DOMContentLoaded', () => {
createApp(document.getElementById('root'))
})
})()
<div id="root"></div>
tileList.flatMap(i => [i, i])
Каждый элемент может находиться в dom-дереве только в одном месте. Если нужны две, то надо его клонировать:
tileList.flatMap(i => [i, i.clone(true)])
