What is the best way to choose and then cancel a selected area?

As a novice following online tutorials, I've managed to create a product color customizer. Currently, the only way to deselect the highlighted area is by clicking outside the image.

But how can I select and highlight an area, then click on it again to deselect that specific region?

const overlays = [];
document.querySelectorAll(".product").forEach(function (path) {
  path.onclick = chooseProduct;
});

function chooseProduct(e) {
  overlays.push(e.target);
  overlays.forEach((overlay) => overlay.classList.add("highlight"));
}

var removeHighlight = function (e) {
  var products = document.querySelectorAll(".product");

  if (
    !e.target.classList.contains("product") &&
    !e.target.classList.contains("color")
  ) {
    overlays.length = 0;
    document.querySelectorAll(".product").forEach(function (prod) {
      prod.classList.remove("highlight");
    });
  }
};
document.onclick = removeHighlight;

var el = document.getElementsByClassName("color");
for (var i = 0; i < el.length; i++) {
  el[i].onclick = changeColor;
}

function changeColor(e) {
  // get the hex color
  let hex = e.target.getAttribute("data-hex");
  overlays.forEach((overlay) => (overlay.style.fill = hex));
}
body,
html {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
}

#container {
  height: 200px;
  width: 200px;
}

#product-svg {
  position: relative;
  z-index: 2;
  background-size: 100%;
  background-repeat: no-repeat;
  background-position: 50%;
  mix-blend-mode: multiply;
}

path {
  fill: #cccccc;
}

#background-image {
  position: relative;
  z-index: 1;
  width: 85%;
  height: auto;
}

[data-test] {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: start;
  padding-left: 15px;
  padding-right: 15px;
}

[data-test] span.color {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  width: 60px;
  padding-bottom: 9px;
}

[data-test] span.color span {
  height: 23px;
  width: 20px;
  background: var(--color);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  margin-bottom: -6px;
}

[data-test] span.color span:first-child {
  margin-left: 1px;
}

.highlight {
  stroke-width: 10px;
  stroke: #000;
}

.red {
  z-index: 2;
  padding: 100px;
}
<div id="container">
  <svg id="product-svg" viewBox="0 0 744 1074">
    <path id="product-a" class="product" d="M51 207.5L51 348L686 348L686 67L51 67L51 207.5Z" />
    <path id="product-b" class="product" d="M51 544.5L51 685L686 685L686 404L51 404L51 544.5Z" />
    <path id="product-c" class="product" d="M51 883.5L51 1024L686 1024L686 743L51 743L51 883.5Z" />

  </svg>
  <img>
</div>
<div class="text"><span class="product-number"></span></div>
<</div>

</main>

<section class="color-select">

  <div data-test>
    <span class="color red">
      <span class="color-selected" style="--color: #ff0000 " data-hex="#ff0000"> </span>
      <span class="color-selected" style="--color: #660000 " data-hex="#660000"> </span>
      <span class="color-selected" style="--color: #990000 " data-hex="#990000"> </span>
      <span class="color-selected" style="--color: #cc0000 " data-hex="#cc0000"> </span>
      <span class="color-selected" style="--color: #ff6666 " data-hex="#ff6666"> </span>
      <span class="color-selected" style="--color: #ff9999 " data-hex="#ff9999"> </span>
      <span class="color-selected" style="--color: #ffcccc " data-hex="#ffcccc"> </span>
    </span>

  </div>
</section>

Answer №1

I modified the code for the chooseProduct function to achieve the desired outcome. While you may want to refactor, rename, and abstract this functionality further, it currently fulfills your requirements:

function chooseProduct(e) {
  // Loop through the overlays...
  for (let i = 0; i < overlays.length; i += 1) {
    let currentOverlay = overlays[i];
    // If the clicked item is already in overlays...
    if (currentOverlay.isSameNode(e.target)) {
      // Remove it from overlays...
      overlays.splice(i, 1);
      // Remove its highlight class...
      e.target.classList.remove('highlight')
      // Exit the function to prevent re-adding it
      return;
    }
  }

  overlays.push(e.target);
  overlays.forEach((overlay) => overlay.classList.add("highlight"));

}

You can test it out with the following snippet:

const overlays = [];
document.querySelectorAll(".product").forEach(function(path) {
  path.onclick = chooseProduct;
});

function chooseProduct(e) {
  // Loop through the overlays...
  for (let i = 0; i < overlays.length; i += 1) {
    let currentOverlay = overlays[i];
    // If the clicked item is already in overlays...
    if (currentOverlay.isSameNode(e.target)) {
      // Remove it from overlays...
      overlays.splice(i, 1);
      // Remove its highlight class...
      e.target.classList.remove('highlight')
      // Exit the function to prevent re-adding it
      return;
    }
  }

  overlays.push(e.target);
  overlays.forEach((overlay) => overlay.classList.add("highlight"));

}

var removeHighlight = function(e) {
  var products = document.querySelectorAll(".product");

  if (!e.target.classList.contains("product") &&
    !e.target.classList.contains("color")
  ) {
    overlays.length = 0;
    document.querySelectorAll(".product").forEach(function(prod) {
      prod.classList.remove("highlight");
    });
  }
};
document.onclick = removeHighlight;

var el = document.getElementsByClassName("color");
for (var i = 0; i < el.length; i++) {
  el[i].onclick = changeColor;
}

function changeColor(e) {
  // Get the hex color
  let hex = e.target.getAttribute("data-hex");
  overlays.forEach((overlay) => (overlay.style.fill = hex));
}
body,
html {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
}

#container {
  height: 200px;
  width: 200px;
}

#product-svg {
  position: relative;
  z-index: 2;
  background-size: 100%;
  background-repeat: no-repeat;
  background-position: 50%;
  mix-blend-mode: multiply;
}

path {
  fill: #cccccc;
}

#background-image {
  position: relative;
  z-index: 1;
  width: 85%;
  height: auto;
}

[data-test] {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: start;
  padding-left: 15px;
  padding-right: 15px;
}

[data-test] span.color {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  width: 60px;
  padding-bottom: 9px;
}

[data-test] span.color span {
  height: 23px;
  width: 20px;
  background: var(--color);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  margin-bottom: -6px;
}

[data-test] span.color span:first-child {
  margin-left: 1px;
}

.highlight {
  stroke-width: 10px;
  stroke: #000;
}

.red {
  z-index: 2;
  padding: 100px;
}
<div id="container">
  <svg id="product-svg" viewBox="0 0 744 1074">
    <path id="product-a" class="product" d="M51 207.5L51 348L686 348L686 67L51 67L51 207.5Z" />
    <path id="product-b" class="product" d="M51 544.5L51 685L686 685L686 404L51 404L51 544.5Z" />
    <path id="product-c" class="product" d="M51 883.5L51 1024L686 1024L686 743L51 743L51 883.5Z" />

  </svg>
  <img>
</div>
<div class="text"><span class="product-number"></span></div>
</div>

<div class="mySlides fade">
  <img>
  <div class="text"><span class="product-number"></span></div>
</div>

<div class="mySlides fade">
  <img>
  <div class="text"><span class="product-number"></span></div>
</div>

</div>

</main>

<section class="color-select">

  <div data-test>
    <span class="color red">
      <span class="color-selected" style="--color: #ff0000 " data-hex="#ff0000"></span>
    <span class="color-selected" style="--color: #660000 " data-hex="#660000"></span>
    <span class="color-selected" style="--color: #990000 " data-hex="#990000"></span>
    <span class="color-selected" style="--color: #cc0000 " data-hex="#cc0000"></span>
    <span class="color-selected" style="--color: #ff6666 " data-hex="#ff6666"></span>
    <span class="color-selected" style="--color: #ff9999 " data-hex="#ff9999"></span>
    <span class="color-selected" style="--color: #ffcccc " data-hex="#ffcccc"></span>
    </span>

  </div>
</section>

Answer №2

  1. Ensure the product class is clicked to verify the presence of the highlight class.
  2. If the highlight class exists, remove it.
  3. If the highlight class does not exist, then add it.

const product_box = document.getElementsByClassName('product');
const color_piker = document.getElementsByClassName('color-selected');

for (const box of product_box) {
    box.addEventListener('click', function (e) {
        if (box.classList.contains('highlight')) {
            box.classList.remove('highlight');
        } else {
            box.classList.add('highlight');
        }
    });
}

document.addEventListener('click', function (e) {
    const target = e.target;
    if (!target.classList.contains('product') && !target.classList.contains('color-selected')) {
        for (const box of product_box) {
            box.classList.remove('highlight');
        }
    }
});

for (const color of color_piker) {
    color.addEventListener('click', function (e) {
        const color_value = color.getAttribute('data-hex');
        const product_box = document.querySelectorAll('.product.highlight');

        for (const product of product_box) {
            product.style.fill = color_value;
            product.classList.remove('highlight');
        }
    });
}
body,
html {
    margin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
}

#container {
    height: 200px;
    width: 200px;
}

#product-svg {
    position: relative;
    z-index: 2;
    background-size: 100%;
    background-repeat: no-repeat;
    background-position: 50%;
    mix-blend-mode: multiply;
}

path {
    fill: #cccccc;
}

#background-image {
    position: relative;
    z-index: 1;
    width: 85%;
    height: auto;
}

[data-test] {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: start;
    padding-left: 15px;
    padding-right: 15px;
}

[data-test] span.color {
    flex-shrink: 0;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    width: 60px;
    padding-bottom: 9px;
}

[data-test] span.color span {
    height: 23px;
    width: 20px;
    background: var(--color);
    clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
    margin-bottom: -6px;
}

[data-test] span.color span:first-child {
    margin-left: 1px;
}

.highlight {
    stroke-width: 10px;
    stroke: #000;
}

.red {
    z-index: 2;
    padding: 100px;
}
<div id="container">
    <svg id="product-svg" viewBox="0 0 744 1074">
        <path id="product-a" class="product" d="M51 207.5L51 348L686 348L686 67L51 67L51 207.5Z" />
        <path id="product-b" class="product" d="M51 544.5L51 685L686 685L686 404L51 404L51 544.5Z" />
        <path id="product-c" class="product" d="M51 883.5L51 1024L686 1024L686 743L51 743L51 883.5Z" />
    </svg>
    <img>
</div>
<div class="text"><span class="product-number"></span></div>
</div>
<div class="mySlides fade">
    <img>
    <div class="text"><span class="product-number"></span></div>
</div>
<div class="mySlides fade">
    <img>
    <div class="text"><span class="product-number"></span></div>
</div>
</div>
</main>
<section class="color-select">
    <div data-test>
        <span class="color red">
        <span class="color-selected" style="--color: #ff0000 " data-hex="#ff0000"></span>
        <span class="color-selected" style="--color: #660000 " data-hex="#660000"></span>
        <span class="color-selected" style="--color: #990000 " data-hex="#990000"></span>
        <span class="color-selected" style="--color: #cc0000 " data-hex="#cc0000"></span>
        <span class="color-selected" style="--color: #ff6666 " data-hex="#ff6666"></span>
        <span class="color-selected" style="--color: #ff9999 " data-hex="#ff9999"></span>
        <span class="color-selected" style="--color: #ffcccc " data-hex="#ffcccc"></span>
        </span>
    </div>
</section>

Answer №3

To switch the highlight class, you'll need to utilize the toggle function within classList. Here is the updated code snippet, with some name changes included:

const overlays = [];

document.querySelectorAll(".item").forEach((path) => {
  path.addEventListener("click", toggleItemHighlight);
});

function toggleItemHighlight({ target }) {
  const highlightClass = "highlight";
  
  target.classList.toggle(highlightClass);
  
  if(!target.classList.contains(highlightClass)) overlays.push(target);
}

var removeHighlight = function ({ target }) {
  if (
    !target.classList.contains("item") &&
    !target.classList.contains("category")
  ) {
    overlays.length = 0;
    document.querySelectorAll(".item").forEach((prod) => {
      prod.classList.remove("highlight");
    });
  }
};

document.onclick = removeHighlight;

const categoriesEl = document.getElementsByClassName("category");

function changeCategoryColor({ target }) {
  const hexColor = target.getAttribute("data-hex");

  overlays.forEach((overlay) => (overlay.style.fill = hexColor));
}

[...categoriesEl].forEach((el) => el.addEventListener("click", changeCategoryColor));
body,
html {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
}

#container {
  height: 200px;
  width: 200px;
}

#item-svg {
  position: relative;
  z-index: 2;
  background-size: 100%;
  background-repeat: no-repeat;
  background-position: 50%;
  mix-blend-mode: multiply;
}

path {
  fill: #cccccc;
}

#background-image {
  position: relative;
  z-index: 1;
  width: 85%;
  height: auto;
}

[data-test] {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: start;
  padding-left: 15px;
  padding-right: 15px;
}

[data-test] span.category {
  flex-shrink: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  width: 60px;
  padding-bottom: 9px;
}

[data-test] span.category span {
  height: 23px;
  width: 20px;
  background: var(--color);
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  margin-bottom: -6px;
}

[data-test] span.category span:first-child {
  margin-left: 1px;
}

.highlight {
  stroke-width: 10px;
  stroke: #000;
}

.red {
  z-index: 2;
  padding: 100px;
}
<div id="container">
  <svg id="item-svg" viewBox="0 0 744 1074">
    <path id="item-a" class="item" d="M51 207.5L51 348L686 348L686 67L51 67L51 207.5Z" />
    <path id="item-b" class="item" d="M51 544.5L51 685L686 685L686 404L51 404L51 544.5Z" />
    <path id="item-c" class="item" d="M51 883.5L51 1024L686 1024L686 743L51 743L51 883.5Z" />

  </svg>
  <img>
</div>
<div class="text"><span class="item-number"></span></div>
</div>

<div class="myItems fade">
  <img>
  <div class="text"><span class="item-number"></span></div>
</div>

<div class="myItems fade">
  <img>
  <div class="text"><span class="item-number"></span></div>
</div>

</div>

</main>

<section class="category-select">

  <div data-test>
    <span class="category red">
      <span class="category-selected" style="--color: #ff0000 " data-hex="#ff0000"></span>
      <span class="category-selected" style="--color: #660000 " data-hex="#660000"></span>
      <span class="category-selected" style="--color: #990000 " data-hex="#990000"></span>
      <span class="category-selected" style="--color: #cc0000 " data-hex="#cc0000"></span>
      <span class="category-selected" style="--color: #ff6666 " data-hex="#ff6666"></span>
      <span class="category-selected" style="--color: #ff9999 " data-hex="#ff9999"></span>
      <span class="category-selected" style="--color: #ffcccc " data-hex="#ffcccc"></span>
    </span>

  </div>
</section>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I need some help with adjusting the number of rows shown per page in MaterialReactTable

I've been utilizing MaterialReactTable and my goal is to display only 5 items on each pagination page. Despite setting muiTablePaginationProps, I still see 10 items per page. How can I resolve this issue? <MaterialReactTable columns={columns} ...

What is the method for indicating the data type in an XDR request?

Currently, I am successfully using XDR for cross domain resource sharing in IE. However, I am facing an issue with specifying the return dataType to receive JSON as responseText. Below is the snippet of my code: if (window.XDomainRequest && $.browser.m ...

Django informing me that the template is not found

Why am I getting a "template does not exist" error in Django? I'm trying to navigate from film_detail.html to film_report.html by clicking on the "report" button... views.py class FilmReport(LoginRequiredMixin, UpdateView): model = Report fi ...

What is the best way to delete a particular item from a local storage array in AngularJS?

This is the HTML code I have: <tr ng-repeat="student in students track by $index"> //some code here <button ng-click="remove(student)">Delete</button> </td> </tr> Then, in my .js ...

When the collapsed navbar is displayed, elements are pushed beyond the boundaries of their parent container (Bootstrap 5)

Introduction Utilizing Bootstrap 5 (included with webpack 5), I am implementing the grid system and collapse function to design the homepage of this website, featuring 2 sidebars that collapse into a top bar. On mobile devices, the navigation collapses a ...

Move your cursor over a specific element to change the div located before it, rather than the one after

Is it possible to have <div> B affect <div> A on hover, instead of the other way around? I've only seen code for the reverse using (+) in CSS. #b:hover + #a { background: #ccc } <div id="a">Div A</div> <div id="b"> ...

What causes errors in my AJAX request based on the particular file extension?

Utilizing JavaScript and jQuery, I have a function that loads data using $.get(url, function(response){ /* ... */});. The data is retrieved from a text file and handled within the response function of the JavaScript. Recently, I encountered an issue on my ...

Tips for looping through an HTML document using Python to extract information from Wikipedia

I am working on developing a tool to extract data from the Wikipedia API. Within an XML file named wikiepedia.html, I have several links that I want to parse through line by line in order to retrieve information which can then be formatted into JSON. The ...

Prevent a specific item from being included in an Electron list using JavaScript

When extracting filenames from a directory, this code is currently being used: getFilenameList = () => { let select = document.getElementById("filenameSelect"); let options = []; fs.readdirSync(folderUrl).forEach(file => { options.pus ...

What is an alternative method to retrieve form data without relying on bodyParser?

When it comes to accessing posted form data without using bodyParser, what alternatives are available? How exactly does bodyParser grant access to the data in req.body? Additionally, I am curious about the inner workings of this process on a deeper level. ...

Mysterious sayings encircling the words fetched through ajax

If the localhost is pointing to the folder named www, where the structure looks like: www/ file/test.cpp index.html I want to dynamically load the content of test.cpp into index.html and display it with the help of highlight.js. Below is the cod ...

Inserting items into an array based on the user-specified quantity

Suppose I have an array with 8 elements. If the user enters a number greater than the length of the array, how can I dynamically add the remaining items to the list? For example: If my array length is 8 and the user enters 15, how can I add 7 items to th ...

Login validation errors in Devise are not being enforced properly

I have implemented devise for handling login and signup functionalities on my website. However, I am encountering an issue with validations on the sign-in page. Here is the code snippet I am using: sessions/new.html.erb <%= form_for(resource, as: reso ...

Unable to Modify TextField Component in React

Is it possible to attach variables to TextField and have those variables deleted as a whole and not editable? Check out the Codesandbox example HERE code <CardContent> <Autocomplete value={values.variable} options={variables} ...

Utilize jQuery and JavaScript dynamically in an HTML document on iOS devices

Having some trouble with this code snippet and getting it to work as intended. - (void)viewDidLoad { [super viewDidLoad]; _webview.delegate = self; [_webview loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBun ...

Guide on Incorporating Coffeescript into the Node.js Blueprint Framework

Have you checked out Skeleton (https://github.com/dstroot/skeleton) yet? It appears to be a robust framework for node.js, offering all the middleware you need. However, it seems to lack support for coffee script. How can we incorporate it into our project? ...

Using radio buttons and a price slider, a unique jQuery filter for products can be

I have successfully implemented basic functionality to filter products based on price (slider) and radio boxes. However, the current filter system uses OR logic, but I need it to use AND instead. For instance, I want to find a product that is from Brand1, ...

Steps for mocking an async action creator in Redux using Jest

I am currently working on writing a unit test for a redux async action creator using jest. asyncActions.js: const startSignInRequest = () => ({ type: START_SIGNIN_REQUEST }); // this is the action creator for successfully signing in a user export c ...

Embedding a table row within an anchor element in Angular 4

Currently, I am facing an issue with a table that contains [routerLink] on each <tr>. This setup is preventing the "open link in a new tab" option from appearing when I right-click because there is no <a> tag. I attempted to enclose the table r ...

How do you trim a string and display the final 3 characters?

When dealing with a list of objects, I want to ensure that the chain of tasks does not become too long and break the table or appear aesthetically unpleasing. Therefore, my goal is to trim the tasks and display only the last 3. In the image below, multiple ...