Как выбрать несколько ячеек, используя Ctrl + клик

16

У меня есть таблица с номерами. Когда я нажимаю на ячейку в таблице, она переключает активное состояние. Я хочу выбрать одну ячейку и нажать crtl и выбрать другую ячейку, и в результате ячейки между первой и второй станут активными. Как это реализовать?

codepen https://codepen.io/geeny273/pen/GRJXBQP

<div id="grid">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
</div>
const grid = document.getElementById("grid")

grid.onclick = (event) => {
  event.stopPropagation();
  const { className } = event.target;

  if (className.includes('cell')) {
    if (className.includes('active')) {
      event.target.className = 'cell';
    } else {
      event.target.className = 'cell active';
    }  
  }
}

Он должен работать как подсветка смены и работает в обоих направлениях

Greenfield
источник
10
Разве эта функция обычно не выполняется с помощью клавиши Shift?
Wimanicesir
активен по циклу, от lastclickдо, thisclickа также проверить на ctrlклик
Энтони Джек
Существует множество неясных условий: хотите ли вы по-прежнему переключать определенную ячейку или диапазон ячеек, хотите ли вы сделать подсветку по диапазону (/ переключение) диапазона или выбрать одну ячейку и т. Д. Несколько примеров могут убрать ваш вопрос. ..
Том
1
@Wimanicesir Shift обычно выбирает диапазон между началом и текущим, в то время как ctrl добавляет к выбору
Emanuel Vintilă
1
@ EmanuelVintilă Но вопрос требует «ячеек между первым и вторым», что в действительности является ожидаемым поведением клавиши Shift.
Джон Монтгомери

Ответы:

11

Попробуй это:

const cells = document.querySelectorAll(".cell");
let lastClicked;

function handleClick(e) {
  // Toggle class active
  if (e.target.classList.contains("active")) {
    e.target.classList.remove("active");
  } else {
    e.target.classList.add("active");
  }

  // Check if CTRL key is down and if the clicked cell has aready class active
  let inRange = false;
  if (e.ctrlKey && this.classList.contains("active")) {
    // loop over cells
    cells.forEach(cell => {
      // check for the first and last cell clicked
      if (cell === this || cell === lastClicked) {
        // reverse inRange
        inRange = !inRange;
      }
      // If we are in range, add active class
      if (inRange) {
        cell.classList.add("active");
      }
    });
  }
  // Mark last clicked
  lastClicked = this;
}

cells.forEach(cell => cell.addEventListener("click", handleClick));
#grid {
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(2, 50px);
}

.cell {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px #ccc;
}

.active {
  background-color: #80aaff;
}
<div id="grid">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
</div>

codepen

awran5
источник
6

Я запрограммировал Javascript часть c полностью отличной от вас. Я надеюсь, что вы все еще можете использовать его. Но он делает именно то, что вы просили.

С Shift + Cell вы можете выбрать все ячейки между ними.

var $lastSelected = [],
	container     = $('#grid'),
	collection    = $('.cell');

container.on('click', '.cell', function(e) {
	var that = $(this),
		$selected,
		direction;

	if (e.shiftKey){

		if ($lastSelected.length > 0) {
			 
			if(that[0] == $lastSelected[0]) {
				return false;
			}
      
			direction = that.nextAll('.lastSelected').length > 0 ? 'forward' : 'back';
 
			if ('forward' == direction) {
				// Last selected is after the current selection
				$selected = that.nextUntil($lastSelected, '.cell');
 
			} else {
				// Last selected is before the current selection
				$selected = $lastSelected.nextUntil(that, '.cell');
			}
			 
			collection.removeClass('selected');
			$selected.addClass('selected');
			$lastSelected.addClass('selected');
			that.addClass('selected');
 
		} else {
			$lastSelected = that;
			that.addClass('lastSelected');
			collection.removeClass('selected');
			that.addClass('selected');
		}

	} else {
		$lastSelected = that;
		collection.removeClass('lastSelected selected');
		that.addClass('lastSelected selected');
   }
});
.selected {background-color: #80aaff;}
#grid{
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(2, 50px);
}

.cell {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="grid">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
</div>

удачи ;)

SwissCodeMen
источник
хотя это не то, что хотел ОП, но приятно попробовать! +1
Мамун Отман
@ Ma'mounothman ... но он хочет, чтобы все ячейки между первым и вторым щелчком отмечались при нажатии клавиши Shift ... и вот как это работает с моим решением. или чего он хочет ??
SwissCodeMen
извините, это не так, дело в том, что ваше решение отменяет выбор отдельных ячеек, но не уверен, что все в порядке, это единственная вещь, которую нужно проверить, кроме того, что ваше решение работает в любом случае!
Мамун Отман
5

Использование previousElementSiblingиcompareDocumentPosition()

const grid = document.getElementById("grid");
const cells = [...grid.querySelectorAll(".cell")];
let recentActive;

grid.onclick = event => {
  event.stopPropagation();
  const { className } = event.target;

  if (!className.includes("cell")) {
    return;
  }

  let compareMask = recentActive && recentActive.compareDocumentPosition(event.target);
  let property = compareMask == 2 ? "nextElementSibling" : "previousElementSibling";

  let state = event.target.classList.toggle("active");
  let sibiling = event.target[property];

  while (event.ctrlKey && state && !sibiling.classList.contains("active")) {
    sibiling.classList.add("active");
    sibiling = sibiling[property];
  }
  recentActive = event.target;
};

Рабочая Демо

https://codepen.io/aswinkumar863/pen/QWbVVNG

User863
источник
Не работает в обратном порядке, например, выберите 6 и Ctrl + выберите 2
Анураг Шривастава
@AnuragSrivastava исправлено
User863
4

Комплексное решение с прямой и обратной функциональностью:

const grid = document.getElementById("grid");
var lastactive = "";

grid.onclick = (event) => {
  event.stopPropagation();
  const { className } = event.target;
  
  if (className.includes('cell')) {
    if (className.includes('active')) {
      event.target.className = 'cell';
      if(lastactive != "" && event.target === lastactive) {
        lastactive = "";
        let cells = document.querySelectorAll('.cell');
        for(let i = 0; i < cells.length; i++) {
          if(cells[i].className.includes('active')) {
            lastactive = cells[i];
            break;
          }
        }
      }
    } 
    else {
      event.target.className = 'cell active';
      if(event.ctrlKey && lastactive != "") {
        let current = event.target;
        if(event.target.compareDocumentPosition(lastactive) == 4 /*event target is before or after last active?*/) {
          while(current != lastactive) {
             current.className = 'cell active';
             current = current.nextElementSibling;
          }
        }
        else {
          while(current != lastactive) {
             current.className = 'cell active';
             current = current.previousElementSibling;
          }
        }
      }
      lastactive = event.target;
    }  
  }
  console.log(lastactive);
}
#grid {
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(3, 50px);
}

.cell {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px #ccc;
  cursor: pointer;
  user-select: none;
}

.active {
  background-color: #80aaff;
}
<div id="grid">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
  <div class="cell">7</div>
  <div class="cell">8</div>
  <div class="cell">9</div>
</div>

МиК
источник
4

Я создал, сохранив индекс выбранного элемента. Это работает в обоих направлениях (2 -> 6) и (6-> 2)

const grid = document.getElementById("grid")

var cells = []

function activate_cell(min, max) {

	for (var i = 0; i < grid.children.length; i++) {
		// Clear all selection
		var el = Array.from(grid.children)[i]
		el.classList.remove("active");
	}
	for (var i = min; i <= max; i++) {
		var el = Array.from(grid.children)[i]
		el.classList.toggle("active");
	}
}
grid.onclick = (event) => {
	event.stopPropagation();
	const { className } = event.target;

	const index = Array.from(grid.children).indexOf(event.target)
	cells.push(index)
	if (event.ctrlKey) {
		activate_cell(Math.min(...cells), Math.max(...cells))
	} else {
		cells.length = 0  // Empty selection if ctrl is not pressed
		cells.push(index)
		activate_cell(Math.min(...cells), Math.max(...cells))
	}
}
#grid {
	display: grid;
	grid-template-columns: repeat(3, 50px);
	grid-template-rows: repeat(2, 50px);
}

.cell {
	display: flex;
	justify-content: center;
	align-items: center;
	border: solid 1px #ccc;
}

.active {
	background-color: #80aaff;
}
<div id="grid">
	<div class="cell">1</div>
	<div class="cell">2</div>
	<div class="cell">3</div>
	<div class="cell">4</div>
	<div class="cell">5</div>
	<div class="cell">6</div>
</div>

CaffeinatedCod3r
источник
4

Выберите один или интервал, но если вы нажмете Ctrl и нажмете 3-й раз, предыдущий выбор сбрасывается, и новый начинается с 1-го элемента (не так сложно расширить)

const grid = document.getElementById("grid")
var previousCell = [];

function toggle(event) {
  event.stopPropagation();
  var target = event.target;

  if (target.className.indexOf('cell') > -1) {
    var cells = target.parentElement.getElementsByClassName("cell");
    if (event.ctrlKey || previousCell[0] == previousCell[1]) {
      if (!event.ctrlKey) previousCell = [];
      previousCell.push(target);
      prepareRange(cells, previousCell);
      switchRange(cells, previousCell);
      previousCell = [target];
      prepareRange(cells, previousCell);
    }
    document.getElementById("range").innerText = previousCell[0]+1;
  }
}
function prepareRange(cells, previousCells) {
  for(var i=0;i<cells.length;i++) {
    var pos = previousCell.indexOf(cells[i]);
    if (pos > -1 && previousCell.length < 4) {
      previousCell.push(i);
    }
  }
  if (previousCell.length == 2) {
    previousCell[0] = previousCell[1];
  } else {
    previousCell[1] = previousCell.pop();
    previousCell.pop();
    previousCell.sort();
  }
}
function switchRange(cells, previousCells) {
  for(var i = previousCells[0];i <= previousCells[1]; i++) {
    target = cells[i];
    if (target.className.indexOf('active') > -1) {
      target.className = 'cell';
    } else {
      target.className = 'cell active';
    }
    if (previousCell.length == 1) break;
  }
}
#grid {
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(2, 50px);
}

.cell {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px #ccc;
}

.active {
  background-color: #80aaff;
}
<div id="grid" onclick="toggle(event)">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
</div>
Last cell:<div id="range"></div>

Том
источник
4

С небольшой модификацией вы можете сделать это так:

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title></title>
        <style>
            #grid {
              display: grid;
              grid-template-columns: repeat(3, 50px);
              grid-template-rows: repeat(2, 50px);
            }

            .cell {
              display: flex;
              justify-content: center;
              align-items: center;
              border: solid 1px #ccc;
            }

            .active {
              background-color: #80aaff;
            }
        </style>
        <script>
            document.addEventListener('DOMContentLoaded',e=>{
                const grid = document.getElementById('grid')
                const cells= grid.querySelectorAll('div');

                grid.addEventListener('click',function(e){
                    e.stopPropagation();

                    cells.forEach( cell=>{
                        cell.classList.remove('active')
                    });
                    event.target.classList.add('active');

                    if( event.ctrlKey ) {
                        Array.from(cells).some( cell=>{
                            cell.classList.add('active')
                            if( cell==event.target )return true;
                        })
                    }
                });
            });
        </script>
    </head>
    <body>
        <div id="grid">
          <div class="cell">1</div>
          <div class="cell">2</div>
          <div class="cell">3</div>
          <div class="cell">4</div>
          <div class="cell">5</div>
          <div class="cell">6</div>
        </div>
    </body>
</html>

Следуя комментарию о том, что это не работает задом наперед, я немного перефразировал оригинал, чтобы он работал в обоих направлениях выбора. Отредактированная версия использует datasetатрибуты - в этом случае назначаются как целые числа. Ведется запись о нажатии начальной ячейки, и, если ctrlнажата клавиша, выполняется простой расчет, чтобы определить, выбирает ли пользователь вперед или назад, что, в свою очередь, влияет на используемый цикл и, следовательно, на присвоение активного класса. Небольшая настройка CSS с использованием переменных была просто для удобства ...

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8' />
        <title></title>
        <style>
            :root{
                --rows:2;
                --cols:3;
                --size:50px;
            }
            #grid {
              display:grid;
              grid-template-columns:repeat(var(--cols),var(--size));
              grid-template-rows:repeat(var(--rows),var(--size));
              width:calc(var(--size) * var(--cols));
            }

            .cell {
              display: flex;
              flex:1;
              justify-content: center;
              align-items: center;
              border: solid 1px #ccc;
              margin:1px;
              cursor:pointer;
            }

            .active {
              background-color: #80aaff;
            }
        </style>
        <script>
            document.addEventListener('DOMContentLoaded',e=>{

                let range=[];

                const grid  = document.getElementById('grid')
                const cells = grid.querySelectorAll('div');

                const getcell=function(i){
                    return grid.querySelector('[data-index="'+i+'"]');
                }
                const clickhandler=function(e){
                    e.stopPropagation();
                    range.push( e.target );

                    /* clear cells of the "active" class */
                    cells.forEach( cell=>{
                        cell.classList.remove('active')
                    });
                    /* Assign the initially selected cell as "active" */
                    e.target.classList.add('active');


                    if( e.ctrlKey ) {
                        /* Is the user selecting forwards or backwards? */
                        if( range[0].dataset.index < e.target.dataset.index ){
                            for( let i=range[0].dataset.index; i < e.target.dataset.index; i++ )getcell(i).classList.add('active')
                        } else if( range[0].dataset.index == e.target.dataset.index ){
                            e.target.classList.add('active')
                        } else {
                            for( let i=range[0].dataset.index; i > e.target.dataset.index; i-- )getcell(i).classList.add('active')
                        }

                        range=[];
                    }
                };

                /* assign an integer index to each cell within parent */
                cells.forEach( ( cell, index )=>{
                    cell.dataset.index = index + 1;
                });

                grid.addEventListener( 'click', clickhandler );
            });
        </script>
    </head>
    <body>
        <div id="grid">
          <div class="cell">1</div>
          <div class="cell">2</div>
          <div class="cell">3</div>
          <div class="cell">4</div>
          <div class="cell">5</div>
          <div class="cell">6</div>
        </div>
    </body>
</html>

RamRaider
источник
Не работает в обратном порядке, например, выберите 6 и Ctrl + выберите 2
Анураг Шривастава
не знал, что это нужно сделать
RamRaider
Правильно, кому в любом случае нужно комплексное решение, амирит? :)
Анураг Шривастава
1
почему это касается тебя? Это вне моего контроля, что происходит после того, как я публикую что-то и полностью вниз для других пользователей, идет ли речь о повышении или понижении ... если только вы не предлагаете какие-либо покерные игры от моего имени, в этом случае это не так.
RamRaider
3

Если вы открыты для jquery, вот решение. Обратите внимание, что это не работает в обратном выборе

$(() => {
  $(".cell").on("click", function(e) {
    $(this).toggleClass("active")
    if (e.ctrlKey) {
      $(this).prevUntil(".active").addClass("active")
    }
  })
})
#grid {
  display: grid;
  grid-template-columns: repeat(3, 50px);
  grid-template-rows: repeat(2, 50px);
}

.cell {
  display: flex;
  justify-content: center;
  align-items: center;
  border: solid 1px #ccc;
}

.active {
  background-color: #80aaff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="grid">
  <div class="cell">1</div>
  <div class="cell">2</div>
  <div class="cell">3</div>
  <div class="cell">4</div>
  <div class="cell">5</div>
  <div class="cell">6</div>
</div>

Анураг Шривастава
источник
на самом деле это тоже не работает наоборот
RamRaider
Мой ответ ясно говорит об этом
Анураг Шривастава