#StackBounty: #javascript #performance #html #react.js #jsx Reduce multiple re rendering of components in React

Bounty: 50

Following is my working application that contains a Search Container, a Sorting Container and a listing Container.

Based on Search and Sort the listing data changes in the listing container.

The application works fine though I noticed in my Search container whenever I am changing the input value in an input box, select component is re rendering and vice versa(check console).

Is there any pattern or modification I can do to minimise the re renders in the application ?

Working Demo – https://ojlrd.csb.app/

Codesandbox – https://codesandbox.io/s/rerender-ojlrd?file=/src/index.js


Code –

Page.js

const Page = () => {
  const [pageData, setPageData] = useState({
    search: {},
    sort: {
      sortBy: ''
    }
  });

  const getData = useCallback((type, data) => {
    setPageData(prevVal => {
      return {
        ...prevVal,
        [type]: {...data}

      }
    })
  }, [setPageData]);
  

  return (
    <div className="container">
      <SearchContainer getData={getData} />
      <SortingContainer getData={getData} />
      <ListingContainer urlData={pageData} />
    </div>
  );
};

export default Page;

SearchContainer.js

const SearchContainer = ({
  getData = () => {}
}) => {
  const [searchData, setSearchData] = useState({
    search1: '',
    search2: '13',
    search3: '',
    search4: '44'
  });

  useEffect(() => {
    getData('search', searchData);
  }, [searchData, getData]);

  const eventHandler = e => {
    setSearchData(prevVal => {
      return {
        ...prevVal,
        [e.target.name]: e.target.value
      }
    })
  }

  return (
    <div className="container-search">
      <h3>Search container</h3>
      <div className="container-search-actions">
        <Input
          initialValue=''
          label='Input 1'
          name="search1"
          eventHandler={eventHandler}
        />
        <Select
          label='Select 2'
          name="search2"
          options={[
            {label: 'Label 11', value: '11'},
            {label: 'Label 12', value: '12'},
            {label: 'Label 13', value: '13'},
            {label: 'Label 14', value: '14'}
          ]}
          selectedValue='13'
          eventHandler={eventHandler}
        />
        <Input
          initialValue=''
          label='Input 3'
          name="search3"
          eventHandler={eventHandler}
        />
        <Select
          label='Select 4'
          name="search4"
          options={[
            {label: 'Label 41', value: '41'},
            {label: 'Label 42', value: '42'},
            {label: 'Label 43', value: '43'},
            {label: 'Label 44', value: '44'}
          ]}
          selectedValue='44'
          eventHandler={eventHandler}
        />
      </div>
    </div>
  )
}

export default SearchContainer;

SortingContainer.js

const SortingContainer = ({
  getData = () => {}
}) => {
  const [sortData, setSortData] = useState({
    sortBy: 'desc'
  })

  useEffect(() => {
    getData('sort', sortData);
  }, [sortData, getData]);

  const eventHandler = e => {
    setSortData(prevVal => {
      return {
        ...prevVal,
        [e.target.name]: e.target.value
      }
    })
  }


  return (
    <div className="container-sorting">
      <Select 
        label='Sort By'
        name='sortBy'
        options={[
          {label: 'Ascending', value: 'asc'},
          {label: 'Descending', value: 'desc'}
        ]}
        selectedValue='desc'
        eventHandler={eventHandler}
      />
    </div>
  );
}

export default SortingContainer;

ListingContainer.js

const ListingContainer = ({ urlData }) => {
  const createURL = (urlData) => {
    const { search, sort } = urlData;
    let url = "/api?";

    [search, sort].forEach((element) => {
      for (const [key, value] of Object.entries(element)) {
        url += `&${key}=${value}`;
      }
    });
    return url;
  };
  return (
    <div className="container-listing">
      This is a listing container
      <p>URL Formed - </p>
      <p className="bold">{createURL(urlData)}</p>
    </div>
  );
};

export default ListingContainer;


Get this bounty!!!

#StackBounty: #javascript #html #css #reactjs #image How to load image from shared file on local server?

Bounty: 100

I am receiving data from local server backend and I want to be to load image. I am receiving array of object like this:

[ {
        "t_pdno": "SFC093989",
        "t_mitm": "SLS005251ACL-3382012763-1",
        "t_qrdr": 60,
        "Operations": "10,20,30,40,60,70",
        "path": "\\192.168.1.245\Images\ACL-3382012763-1.jpg"
    },
    {
        "t_pdno": "SFC093991",
        "t_mitm": "SLS005251ACL-3382012765-1",
        "t_qrdr": 120,
        "Operations": "10,20,30,40",
        "path": "\\192.168.1.245\Images\ACL-3382012765-1.jpg"
    },]

After I console.log(rowData.path) the path it looks like this:

\192.168.1.245ImagesACL-3382014766-1.jpg

So it is perfect to paste in browser and I get the image:

enter image description here

The problem is I cannot load it in my img tag. I tried:

<img
    src={process.env.PUBLIC_URL + rowData.path}
    alt={`${rowData.t_mitm}`}
     loading="lazy"
              />

<img
     src={rowData.path}
     alt={`${rowData.t_mitm}`}
     loading="lazy"
              />




  <img
      src={require(rowData.path)}
      alt={`${rowData.t_mitm}`}
      loading="lazy"
                  />



 <img
      src={`url(rowData.path)`}
      alt={`${rowData.t_mitm}`}
      loading="lazy"
                  />

and nothing is working. How can I load the images?

UPDATE:

If I install http-server to the \192.168.1.245 server and host the Images folder there on specific port I am able to receive the image. But this mean that I will always have to keep folder hosted.

UPDATE 2:

If I try loading the image like this:

   <img
          src={`file://192.168.1.245/Images/${rowData.t_mitm}`}
          alt={`${rowData.t_mitm}`}
          loading="lazy"
                      />

It probably works but I get:

Not allowed to load local resource:
file://192.168.1.245/Images/ACL-3382012763-1.jpg


Get this bounty!!!

#StackBounty: #php #html #wordpress How to loop php with multiple styling div on WordPress

Bounty: 50

I am working on a WP theme and stumbled into a specific task which I can’t describe here but I am sure if I show some screenshots here then you can understand the issue.

First of all please see the below image for posts div structure

enter image description here

It is the actual design from HTML, and specifically the first 2 posts inside a div and the third post inside another div like the below screenshot

enter image description here

please put a comment if you can’t understand this.

The question is how to loop that dynamically by keeping the same structure and design?

And here is my codes below

<div class="row justify-content-center">
    <div class="col-xl-3 col-lg-6 col-md-6">
        <?php
        while($projects_array->have_posts()):
            $projects_array->the_post();

            $idd = get_the_ID();
            $terms = wp_get_post_terms(get_the_ID(), 'project_cat');

            $output = array();
            if ($terms) {
                $i = 1;
                foreach ($terms as $term) {
                    if($i == 1):
                        $output[] = '<span class="tag-'.$i.'">'.$term->name.'</span>';
                        $id[] = $term->term_id ;
                    endif;
                    $i++;
                }
            }

            if ( class_exists('ACF') && get_field('choose_link_type') == 1 ) {
                $post_link = get_the_permalink();
            } else {
                $post_link = get_field('external_link');
            }
            ?>
                <div class="single-portfolio-box">
                    <?php if(has_post_thumbnail()): ?>
                        <img src="<?php the_post_thumbnail_url(); ?>" alt="<?php the_post_thumbnail_caption(); ?>">
                    <?php endif; ?>
                    <div class="content">
                        <h3><a href="<?php echo esc_url( $post_link ); ?>"><?php the_title(); ?></a></h3>
                        <?php echo join( ' ', $output ); ?>
                    </div>
                </div>
        <?php endwhile; ?>
        <?php wp_reset_query(); ?>
    </div>
</div>

Thanks in advance.


Get this bounty!!!

#StackBounty: #html #css #flexbox #reactstrap How do I right align a ButtonDrowpdown inside a ModalHeader with reactstrap?

Bounty: 50

I am pretty happy with the Modal that I have so far, except that the dropdown is not where I want it to be. I want it farther right, just to the left of the ‘x’ button. Here is the code I have so far:

<Modal isOpen={modal} toggle={toggleModal} className="modal-lg modal-dialog-scrollable" returnFocusAfterClose={false}>
    <ModalHeader toggle={toggleModal}>
        <span className="align-middle mr-auto">Modal Title</span>
        <ButtonDropdown isOpen={dropdownOpen} toggle={toggleDropdown}>
            <DropdownToggle caret color="info" className="btn-sm">
                Dropdown
            </DropdownToggle>
            <DropdownMenu>
                <DropdownItem header>Header</DropdownItem>
                <DropdownItem disabled>Action</DropdownItem>
                <DropdownItem>Another Action</DropdownItem>
                <DropdownItem divider />
                <DropdownItem>Another Action</DropdownItem>
            </DropdownMenu>
        </ButtonDropdown>
    </ModalHeader>
    <ModalBody>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </ModalBody>
</Modal>

Where I want the dropdown

I have tried to use bootstrap classes with a flex-row div around the stuff inside the ModalHeader and ml-auto on the ButtonDropdown but that didn’t work for me.

Update 1
After some time looking into this, I still can’t figure out how to solve this. I do know that the reactstrap source code is wrapping everything in a modal-title div before wrapping the modal-header around that (I found that out by looking at ModalHeader.js). To investigate further I wrote a basic example with reactstrap:

<Modal isOpen={modal} toggle={toggleModal}>
    <ModalHeader>
        <div className="d-flex flex-row">
        <h5 className="mr-auto">Test 1</h5>
        <h5>Test 2</h5>
        </div>            
    </ModalHeader>
    <ModalBody>
        Lorem ipsum .
    </ModalBody>
</Modal>

This gets rendered to the following html and css:

<div class="modal-header">
    <h5 class="modal-title">
        <div class="d-flex flex-row">
        <h5 class="mr-auto">Test 1</h5>
        <h5>Test 2</h5>
        </div>
    </h5>
</div>
.modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    padding: 1rem 1rem;
    border-bottom: 1px solid #dee2e6;
    border-top-left-radius: calc(.3rem - 1px);
    border-top-right-radius: calc(.3rem - 1px);
}

.modal-title {
    margin-bottom: 0;
    line-height: 1.5;
}

This still doesn’t work, though: "Test 1" and "Test 2" are right next to each other as opposed to 1 on the left and 2 on the right. I know that if I remove the modal header display:flex that it looks like it’s fixed but I’m not really sure why. I don’t think the modal-title is the issue.


Get this bounty!!!

#StackBounty: #javascript #html #iframe #frameset Replicating nested and togglable <frameset> in HTML5

Bounty: 100

I’m given to understand that the <frameset> tag is deprecated as of HTML5. Thankfully, Chrome still supports rendering it, and unfortunately, it’s currently the only thing I’ve found that fits my use case.

The important element of the <frameset> tag that other frame-like objects lack is draggable borders, which I haven’t been able to get working with iframes even with a prohibitive amount of javascript assistance.

The other important thing in my case is that one of the frames contains a button/link that causes the other frame to disappear or reappear. When that happens, the frames should resize appropriately to fill the space.

My current HTML looks like the following MVCE:

index.html

<html>
<head>
    <script language="javascript">
        function toggleBottomFrame() {
            var bottomFrame = document.getElementById("bottomFrame");
            var horizFrameset = document.getElementById("horizFrameset");
            if (bottomFrame.style.display == "none") {
                bottomFrame.style.display = "";
                horizFrameset.rows = "*,25%";
            } else {
                bottomFrame.style.display = "none";
                horizFrameset.rows = "*,0px";
            }
        }
        document.toggleBottomFrame = toggleBottomFrame;
    </script>
</head>
<frameset id="horizFrameset" rows="*,0px">
    <frameset id="vertFrameset" cols="300px,*">
        <frame id="topLeftFrame" src="buttonpage.html"></frame>
        <frame id="topRightFrame"></frame>
    </frameset>
    <frame id="bottomFrame" style="display:none"></frame>
</frameset>
</html>

buttonpage.html

<html>
<head></head>
<body>
    <button onclick="parent.frameElement.ownerDocument.toggleBottomFrame();">
</body>
</html>

How do I implement the exact same functionality (including, most importantly, the ability to drag around the borders of the frames with my mouse to expand or shrink one of the frames) using non-deprecated functionality?

If possible, I’d like a solution in standard client-side JS or HTML, without needing to import another library like resize.js. This is meant for a very lightweight frontend, and I don’t want to bloat it down with libraries I don’t need.


Get this bounty!!!

#StackBounty: #html #css #cross-browser #w3c #browser-bugs Is the change of outline-offset, on pressing any keyboard key on anchor tag,…

Bounty: 50

In the following demo, click and hold the mousedown on the anchor tag, then drag your cursor away, while holding mousedown, and then finally let go of the click. You will see a red dotted outline around the anchor tag. Now if you press shift key the outline will get offset by a few pixels.

a:focus {
  outline: 1px dotted red;
}
<a href="#">Click+hold, then release, then press shift key</a>

The behaviour doesn’t occur on pressing ctrl or fn keys etc, but does happen for most keys. This behaviour seems to be cross browser compatible, which leads me to ponder:

  • Is it a bug in the implementation of html by the broswers?
  • Or, is it the expected behaviour suggested by w3.org for some user experience issues?


Get this bounty!!!

#StackBounty: #javascript #html #svg #fabricjs Fabric.js svg elements to pop up on click to be edited and then return to its previous s…

Bounty: 50

I am developing an app in html5 and I’m using fabric.js to manipulate some graphics. I need to make some of the paths to pop up when clicked in order to be edited on a closer view as their real size is quite small and the app will be display on mobile screens. This is what I have done until now:

  canvas = new fabric.Canvas('c', { preserveObjectStacking: true });
    canvas.selection = false;
    fabric.Object.prototype.borderColor = 'transparent';
    fabric.Object.prototype.cornerColor = '#d50000';
    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.cornerStyle = 'circle';
    canvas.hoverCursor = 'pointer';

    // delete icon
    var deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Cpath style='fill:%23d50000;' d='m 146.8759,307.20476 38.95015,0 114.10246,100.31923 114.10245,-100.31923 38.95016,0 -120.53144,131.86736 120.53144,131.86889 -38.95016,0 -114.10245,-100.31924 -114.10246,100.31924 -38.95015,0 L 267.29322,439.07212 146.8759,307.20476 Z'/%3E%3C/svg%3E";

    var img = document.createElement('img');
    img.src = deleteIcon;

    fabric.Object.prototype.controls.deleteControl = new fabric.Control({
        x: 0.5,
        y: -0.5,
        offsetY: 76,
        offsetX: -15,
        cursorStyle: 'pointer',
        mouseUpHandler: deleteObject,
        render: renderIcon,
        cornerSize: 24
    });

    function deleteObject(eventData, transform) {
        var target = transform.target;
        var canvas = target.canvas;
        canvas.remove(target);
        canvas.requestRenderAll();
    }

    function renderIcon(ctx, left, top, styleOverride, fabricObject) {
    var size = this.cornerSize;
    ctx.save();
    ctx.translate(left, top);
    ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
    ctx.drawImage(img, -size/2, -size/2, size, size);
    ctx.restore();
    }

    // bg

    canvas.backgroundColor= "#ccc";

    //* 31
    var v31 = new fabric.Path('m 305.16587,542.07107 9.80091,-0.98571 7.18636,10.09881 c -5.31084,6.69609 -15.03687,7.83521 -21.68907,2.48023 z');
    v31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1});
    canvas.add(v31);
    v31.set('selectable', false);
    p31 = new fabric.Path('m 305.16587,542.07107 -4.70183,11.59335 c -6.69611,-5.31031 -7.83714,-15.03769 -2.48451,-21.69214 z');
    p31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(p31);
    p31.set('selectable', false);
    var l31 = new fabric.Path('m 314.96678,541.08536 -9.80091,0.98571 -7.31191,-9.93775 c 5.22415,-6.76398 14.93463,-8.02848 21.6553,-2.7598 z');
    l31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(l31);
    l31.set('selectable', false);
    var d31 = new fabric.Path('m 314.96678,541.08536 4.70184,-11.59338 c 6.6961,5.31032 7.83714,15.0377 2.48451,21.69215 z');
    d31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(d31);
    d31.set('selectable', false);
    var group31 = new fabric.Group([ v31, l31, p31, d31 ], {left: 210, top: 386, opacity: 1});

    // 41
    var v41 = new fabric.Path('m 265.11576,542.24785 -9.80091,-0.98571 -7.18636,10.09881 c 5.31083,6.69609 15.03687,7.83521 21.68907,2.48023 z');
    v41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1});
    canvas.add(v41);
    v41.set('selectable', false);
    var p41 = new fabric.Path('m 265.11576,542.24785 4.70182,11.59335 c 6.69612,-5.31031 7.83715,-15.03769 2.48452,-21.69214 z');
    p41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(p41);
    p41.set('selectable', false);
    var l41 = new fabric.Path('m 255.31485,541.26214 9.80091,0.98571 7.31191,-9.93775 c -5.22416,-6.76398 -14.93462,-8.02848 -21.6553,-2.7598 z');
    l41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(l41);
    l41.set('selectable', false);
    var d41 = new fabric.Path('m 255.31485,541.26214 -4.70184,-11.59337 c -6.6961,5.31032 -7.83715,15.03769 -2.48451,21.69214 z');
    d41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(d41);
    d41.set('selectable', false);
    var group41 = new fabric.Group([ v41, l41, p41, d41 ], {left: 173.5, top: 386, opacity: 1});

    // create grid
    function createGrid() {
    canvas.add(new fabric.Path('m 0,49.54498 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,56.08866 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,68.942311 416,0"', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,87.171121 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,110.7751 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,116.61767 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,126.90059 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,137.88462 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,146.08928 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,168.96708 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,172.70632 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,203.08768 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,205.191 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,246.08898 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,247.7249 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,278.57366 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,281.3781 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,304.98207 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,313.39537 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,324.1457 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,334.42862 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,340.50489 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,363.40776 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,382.10398 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,395.19133 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,401.73501 416,0', { opacity: '0', selectable: false }));

    canvas.add(new fabric.Path('m 32.485,0 -3.19e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 39.963,0 1.69e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 54.453,0 -2.61e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 73.383,0 -3.39e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 97.454,0 4.3e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 99.557,0 3.68e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 111.009,0 -2e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 125.498,0 3.7e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 131.341,0 -6e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 156.581,0 -1.6e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 158.45,0 4.6e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 190,0 3.3e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 225.991,0 -4.5e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 257.54,0 4.2e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 259.17583,0 3.4e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 284.65,0 -6e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 290.726,0 2.1e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 304.74837,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 316.43351,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 318.77054,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 343.07562,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 361.77184,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 376.02771,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 383.7399,0 0,441.23077', { opacity: '0', selectable: false }));
    }

    // call this to actually create grids
    createGrid();

    // menu
    var redbtn = new fabric.Path('m 536.95273,607.72242 c 10e-6,9.21831 -7.47292,16.69124 -16.69123,16.69123 -9.21831,1e-5 -16.69123,-7.47292 -16.69123,-16.69123 2e-5,-9.2183 7.47293,-16.6912 16.69123,-16.6912 9.2183,0 16.69123,7.4729 16.69123,16.6912 z');
    redbtn.set({fill: '#d50000', stroke: '#1a1a1a', strokeWidth: 2, opacity: 1, left:363, top:454, hoverCursor: 'pointer' });
    canvas.add(redbtn);
    redbtn.set('selectable', false);

    var badrbtn = new fabric.Path('m 444.06184,672.53045 c -1.15491,3.93506 1.09854,8.06136 5.03317,9.21638 3.93466,1.15501 8.06055,-1.09864 9.21545,-5.0337 1.1549,-3.93505 -1.09853,-8.06135 -5.03318,-9.21637 -3.93466,-1.15501 -8.06054,1.09865 -9.21544,5.03369 z m -0.38452,1.80161 c 0.008,4.13284 3.325,7.52008 7.4597,7.56212 l -0.0905,9.21705 c -9.21992,-0.11093 -16.61428,-7.65898 -16.43826,-16.87185 z m 7.56106,7.70446 c 4.13199,-0.0601 7.47657,-3.4204 7.46616,-7.55577 l 9.21652,-0.0262 c 0.006,9.22152 -7.44695,16.71174 -16.66036,16.65255 z m 7.61272,-7.45432 c -0.008,-4.13282 -3.32498,-7.52008 -7.45971,-7.56213 l 0.0905,-9.21704 c 9.21993,0.11092 16.61428,7.65899 16.43826,16.87186 z m -7.46384,-7.70858 c -4.13241,0.006 -7.52004,3.32371 -7.56298,7.45881 l -9.21608,-0.0931 c 0.11285,-9.22081 7.66177,-16.61429 16.87371,-16.43626 z');
badrbtn.set({fill: '#007aff', stroke: '#d50000', strokeWidth: 2, opacity: 1, left:315, top:514, hoverCursor: 'pointer' });
canvas.add(badrbtn);
badrbtn.set('selectable', false);

    var oligo = new fabric.Path('m 258.83096,607.64956 a 15.904878,15.904878 0 0 1 -15.90488,15.90489 15.904878,15.904878 0 0 1 -15.9049,-15.90489 15.904878,15.904878 0 0 1 15.9049,-15.90488 15.904878,15.904878 0 0 1 15.90488,15.90488 z');
    oligo.set({fill: '#fff', stroke: '#0d47a1', strokeWidth: 2, opacity: 1, left:172, top:471, lockRotation: 'true', originX: 'center',  originY: 'center'});
    canvas.add(oligo);
    oligo.setControlsVisibility({ mt: false, mb: false, ml: false, mr: false, bl: false, br: false, tl: false, tr: false, mtr: false, });

    var rtnd = new fabric.Path('m 258.6723,674.6758 c 10e-6,8.78402 -7.12086,15.9049 -15.90488,15.9049 -8.78403,10e-6 -15.90491,-7.12087 -15.9049,-15.9049 0,-8.78402 7.12088,-15.90489 15.9049,-15.90488 8.78402,0 15.90488,7.12086 15.90488,15.90488 z');
    rtnd.set({fill: '#fff', stroke: '#d50000', strokeWidth: 2, opacity: 1, left:172, top:531, lockRotation: 'true', originX: 'center',  originY: 'center'});
    canvas.add(rtnd);
    rtnd.setControlsVisibility({ mt: false, mb: false, ml: false, mr: false, bl: false, br: false, tl: false, tr: false, mtr: false, });

    // snap to grid
    canvas.on('object:moving', function(options) {
    const horizontalSnappingPoints = options.target.canvas._objects
        .filter((el, index) => options.target.canvas._objects
        .some((sameTopEl, sameTopElIndex) => sameTopEl.top === el.top && sameTopElIndex !== index)).map(item => item.left);
    const verticalSnappingPoints = options.target.canvas._objects
        .filter((el, index) => options.target.canvas._objects
        .some((sameLeftEl, sameLeftElIndex) => sameLeftEl.left === el.left && sameLeftElIndex !== index)).map(item => item.top)
        options.target.set({
        left: horizontalSnappingPoints.reduce(function (prev, curr) {
        return (Math.abs(curr - options.target.left)    < Math.abs(prev - options.target.left) ? curr : prev);
        }),
        top: verticalSnappingPoints.reduce(function (prev, curr) {
        return (Math.abs(curr - options.target.top)     < Math.abs(prev - options.target.top) ? curr : prev);
        }),
    });
    });

    function saveCanvas(save){
    localStorage.setItem("draw", JSON.stringify(canvas.toObject()));
    }

    if (newCanvas){
    var newCanvas = JSON.parse(localStorage.getItem("draw"));
    canvas.loadFromJSON()
    } else {
    canvas = new fabric.Canvas('c', { preserveObjectStacking: true });

    canvas.selection = false;
    fabric.Object.prototype.borderColor = 'transparent';
    fabric.Object.prototype.cornerColor = '#d50000';
    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.cornerStyle = 'circle';
    canvas.hoverCursor = 'pointer';
    }
.controls {
        display: flex;justify-content:center;
    }
    .wrapper {
        width:100%;height:auto;
        position: relative;
        display:flex;
        justify-content:center;
    }
  <head>
    https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js
    </head>
    <body>
    <div class="wrapper">
    <div>
    <canvas id="c" width="416" height="570" style="text-align:center;"></canvas>
    <div>

    <div class="controls">
        <button id="save" onClick="saveCanvas()">Save canvas</button>
    </div>
    </div>
  </body>

Then what I need is:

  1. The 4 elements at the bottom will function as helpers or buttons inside the canvas. The red and blue ones should be color pickers where the user will take the fill color and the stroke color with a click/tap and then pass them to one of the areas on the two upper elements. Each area of the upper elements should be colored independet of the rest of the group.
  2. The other two elements at tthe bottom will function as draggable objects which should be placed on top of the upper elements. (this part is working ok)
  3. The upper elements, as there will be many more of them and the app will be displayed on mobile screens, need to be colored using one of the bottom color pickers. But as the tip of a finger is much bigger than the area to tap it will be necessary to make those upper elements to pop up on a triggering action, which should be fired by a mouse click/touch-screen event. Then they will be colored in an easy way.
  4. The elements when pop up should fill the whole superior half of the screen no matter the position they were before, and, after being edited, should be returned to theier initial position by another triggering action (maybe a tiny close button appended to the pop up). The trigering action should be located in the element itself and not in any external button.

I have added the fabric.js serialization and deserialization in order to save the canvas to the localStorage and later retrieve it from it to go on editing. The problem here is that, if I place the JSON.parse(localStorage.getItem("draw")) and the if else at the beginnig, rigth after the canvas initiation the draggable elements stop working, but if I place it at the bottom, then the draggable items still work but theier final position is not saved and when reloaded the canvas is displayed as if the user have not moved/changed anything. Then I need to:

  1. Use the Save button to store only the colored and the dragged elements according to the user’s choices and later return them to the exact position, with the exact colors they were saved. I enhanced the word only because, if all the elements are to be saved, it will be and enourmous and unnecesary amount of info to be stored once and again, mainly if we take into account that every user should edit and store a lot of this graphics.

NOTE:There will be a lot of this upper items on the canvas and almost
16 items at the bottom menu. I just placed two of each as a sample.

If someone knows how to solve it, or at least a part of it, I will be very thankfull.


Get this bounty!!!

#StackBounty: #javascript #html #jquery #svg #fabricjs Fabric.js svg elements to pop up on click to be edited and then return to its pr…

Bounty: 50

I am developing an app in html5 and need at some point to make the paths from an svg to pop up when clicked in order to be edited on a closer view as thier real size is quite small and it will display on mobile screens. This is what I have done until now:

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta name="HandheldFriendly" content="true"><meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no, width=device-width"><meta name="viewport" content="width=device-width">
</head><body>
<div>
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 570 770" onload="makeDraggable(evt)">
 <defs><style> body { background: #fff;} svg { border-radius: 10px;} .fgcolor { fill: #fff; opacity: 1; stroke:#1a1a1a; stroke-width:1.5; } .selected { fill: #d50000; opacity: 1; stroke:#1a1a1a; stroke-width:1.5; } .selected2 { fill: #0d47a1; opacity: 1; stroke:#1a1a1a; stroke-width:1.5; } .selected3 { fill: #1a1a1a; opacity: 1; stroke:#1a1a1a; stroke-width:1.5; } .selected4 { fill: #0d47a1; opacity: 1; stroke:#d50000; stroke-width:1.5; }.trans { transition: all 2s ease-in-out 0.5s; } #circle1 #circle2 { z-index:1000;height: 100px;width: 100px;text-align: center;margin: 0 auto; } .grow:hover { transform-origin: 512px 110px;transform: scale(10.0);} .static { cursor: not-allowed; } .draggable, .draggable-group { cursor: move; }</style>
    <script type="text/javascript"><![CDATA[
      function makeDraggable(evt) {
        var svg = evt.target;
        svg.addEventListener('mousedown', startDrag);
        svg.addEventListener('mousemove', drag);
        svg.addEventListener('mouseup', endDrag);
        svg.addEventListener('mouseleave', endDrag);
        svg.addEventListener('touchstart', startDrag);
        svg.addEventListener('touchmove', drag);
        svg.addEventListener('touchend', endDrag);
        svg.addEventListener('touchleave', endDrag);
        svg.addEventListener('touchcancel', endDrag);
        var selectedElement, offset, transform,
            bbox, minX, maxX, minY, maxY, confined;
        var boundaryX1 = 10;
        var boundaryX2 = 560;
        var boundaryY1 = 10;
        var boundaryY2 = 760;
        function getMousePosition(evt) {
          var CTM = svg.getScreenCTM();
          if (evt.touches) { evt = evt.touches[0]; }
          return {
            x: (evt.clientX - CTM.e) / CTM.a,
            y: (evt.clientY - CTM.f) / CTM.d
          };
        }
        function startDrag(evt) {
          if (evt.target.classList.contains('draggable')) {
            selectedElement = evt.target;
            offset = getMousePosition(evt);
            // Make sure the first transform on the element is a translate transform
            var transforms = selectedElement.transform.baseVal;
            if (transforms.length === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) {
              // Create an transform that translates by (0, 0)
              var translate = svg.createSVGTransform();
              translate.setTranslate(0, 0);
              selectedElement.transform.baseVal.insertItemBefore(translate, 0);
            }
            // Get initial translation
            transform = transforms.getItem(0);
            offset.x -= transform.matrix.e;
            offset.y -= transform.matrix.f;
            confined = evt.target.classList.contains('confine');
            if (confined) {
                bbox = selectedElement.getBBox();
                minX = boundaryX1 - bbox.x;
                maxX = boundaryX2 - bbox.x - bbox.width;
                minY = boundaryY1 - bbox.y;
                maxY = boundaryY2 - bbox.y - bbox.height;
            }
          }
        }
        function drag(evt) {
          if (selectedElement) {
            evt.preventDefault();
            var coord = getMousePosition(evt);
            var dx = coord.x - offset.x;
            var dy = coord.y - offset.y;
            if (confined) {
                if (dx < minX) { dx = minX; }
                else if (dx > maxX) { dx = maxX; }
                if (dy < minY) { dy = minY; }
                else if (dy > maxY) { dy = maxY; }
            }
            transform.setTranslate(dx, dy);
          }
        }
        function endDrag(evt) {
          selectedElement = false;
        }
      }
    ]]> </script>
    <script type="application/ecmascript"> <![CDATA[

     var defaultAction = "task";

     function setAction(param) {
         defaultAction = param;
        }

        function click(evt) {
         if (evt.target.classList.contains("fgcolor")) {
         if (defaultAction == "circleschart") {
            hotCircleschartClick(evt.target);
         } else {
            hotTaskClick(evt.target);
            }
         }
        }

        function hotTaskClick(hot) {
            var str = hot.getAttribute("id");
            var hot_id = str.substring(1, str.length);

         if (hot.classList.contains('fgcolor')) {
            hot.classList.add('selected');
            }
        }
     ]]> </script></defs>
 <path id="bg" style="fill:#555;fill-opacity:1;fill-rule:nonzero;stroke:#1a1a1a;stroke-width:1;stroke-opacity:1" d="m 569.48439,0.51561093 0,768.96877907 -568.96877907,0 0,-768.96877907 z"/>
  <path id="circlesmask" style="fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:#1a1a1a;stroke-width:1.5;stroke-opacity:1" d="m 494.84341,141.33148 a 9.5804669,9.5804669 0 0 0 3.77981,-13.0109 9.5804669,9.5804669 0 0 0 -13.01092,-3.77981 9.5804669,9.5804669 0 0 0 -3.77978,13.01092 9.5804669,9.5804669 0 0 0 13.01089,3.77979 z m 1.99086,-1.2983 c 3.75292,-3.7878 3.79944,-9.90488 0.0548,-13.70535 l 8.46819,-8.34965 c 8.33458,8.49033 8.23285,22.12334 -0.30978,30.3914 z m 0.0915,-13.92778 c -3.83518,-3.70451 -9.95225,-3.67355 -13.70495,0.11906 l -8.45622,-8.36179 c 8.38404,-8.4415 22.0172,-8.51269 30.39291,-0.0756 z m -13.74675,-0.10709 c -3.75291,3.7878 -3.79945,9.90486 -0.0548,13.70536 l -8.46818,8.34965 c -8.3346,-8.49032 -8.23284,-22.12335 0.30978,-30.39141 z m -0.1842,13.84308 c 3.78702,3.75369 9.90405,3.80157 13.70533,0.0577 l 8.34729,8.47053 c -8.49206,8.33279 -22.12509,8.22809 -30.39133,-0.31635 z m 2.64893,-63.1438 a 9.5804669,9.5804669 0 0 0 -3.82845,12.99668 9.5804669,9.5804669 0 0 0 12.9967,3.82844 9.5804669,9.5804669 0 0 0 3.82842,-12.99669 9.5804669,9.5804669 0 0 0 -12.99667,-3.82843 z m -1.9957,1.29085 c -3.76706,3.77374 -3.83646,9.8906 -0.10601,13.70505 l -8.49937,8.31792 c -8.30277,-8.52144 -8.15005,-22.15398 0.42344,-30.39003 z m -0.1436,13.92734 c 3.82131,3.71883 9.93845,3.71075 13.70531,-0.0678 l 8.42488,8.39336 c -8.41555,8.41008 -22.04888,8.43028 -30.39297,-0.038 z m 13.74626,0.1585 c 3.76705,-3.77374 3.83647,-9.89058 0.10603,-13.70505 l 8.49934,-8.31793 c 8.30279,8.52144 8.15004,22.15399 -0.42345,30.39004 z m 0.23598,-13.84229 C 493.71373,74.46416 487.59692,74.3934 483.78167,78.123 l -8.31556,-8.50169 c 8.52317,-8.30097 22.15571,-8.14528 30.38994,0.43002 z"/>
  <g id="circles" onclick="javascript:click(evt);">
  <g id="circle1" class="trans grow">
    <path class="fgcolor" id="v1" d="M 497.48669,78.23199 C 493.71373,74.46416 487.59692,74.3934 483.78167,78.123 l -8.31556,-8.50169 c 8.52317,-8.30097 22.15571,-8.14528 30.38994,0.43002 z"/>
    <path class="fgcolor" transform-center-y="-0.035180283" transform-center-x="-9.0648938" d="m 497.25071,92.07428 c 3.76705,-3.77374 3.83647,-9.89058 0.10603,-13.70505 l 8.49934,-8.31793 c 8.30279,8.52144 8.15004,22.15399 -0.42345,30.39004 z" id="p1"/>
    <path class="fgcolor" d="m 483.50445,91.91578 c 3.82131,3.71883 9.93845,3.71075 13.70531,-0.0678 l 8.42488,8.39336 c -8.41555,8.41008 -22.04888,8.43028 -30.39297,-0.038 z" transform-center-x="-0.15829128" transform-center-y="9.0368236" id="pa1"/>
    <path class="fgcolor" transform-center-y="0.03518" transform-center-x="9.0648894" d="m 483.64805,77.98844 c -3.76706,3.77374 -3.83646,9.8906 -0.10601,13.70505 l -8.49937,8.31792 c -8.30277,-8.52144 -8.15005,-22.15398 0.42344,-30.39003 z" id="d1"/>
    <path class="fgcolor" d="m 485.64375,76.69759 a 9.5804669,9.5804669 0 0 0 -3.82845,12.99668 9.5804669,9.5804669 0 0 0 12.9967,3.82844 9.5804669,9.5804669 0 0 0 3.82842,-12.99669 9.5804669,9.5804669 0 0 0 -12.99667,-3.82843 z" id="o1"/>
  </g>
  <g id="circle2" class="trans grow">
    <path class="fgcolor" id="v2" d="m 482.99482,139.84139 c 3.78702,3.75369 9.90405,3.80157 13.70533,0.0577 l 8.34729,8.47053 c -8.49206,8.33279 -22.12509,8.22809 -30.39133,-0.31635 z"/>
    <path class="fgcolor" id="p2" d="m 483.17902,125.99831 c -3.75291,3.7878 -3.79945,9.90486 -0.0548,13.70536 l -8.46818,8.34965 c -8.3346,-8.49032 -8.23284,-22.12335 0.30978,-30.39141 z" transform-center-x="-9.0648938" transform-center-y="-0.035180283"/>
    <path class="fgcolor" id="pa2" transform-center-y="9.0368236" transform-center-x="-0.15829128" d="m 496.92577,126.1054 c -3.83518,-3.70451 -9.95225,-3.67355 -13.70495,0.11906 l -8.45622,-8.36179 c 8.38404,-8.4415 22.0172,-8.51269 30.39291,-0.0756 z"/>
    <path class="fgcolor" id="d2" d="m 496.83427,140.03318 c 3.75292,-3.7878 3.79944,-9.90488 0.0548,-13.70535 l 8.46819,-8.34965 c 8.33458,8.49033 8.23285,22.12334 -0.30978,30.3914 z" transform-center-x="9.0648894" transform-center-y="0.03518"/>
    <path class= "fgcolor" id="o2" d="m 494.84341,141.33148 a 9.5804669,9.5804669 0 0 0 3.77981,-13.0109 9.5804669,9.5804669 0 0 0 -13.01092,-3.77981 9.5804669,9.5804669 0 0 0 -3.77978,13.01092 9.5804669,9.5804669 0 0 0 13.01089,3.77979 z"/>
  </g>
  </g>
  <path class="static" id="red" style="opacity:1;fill:#d50000;fill-opacity:1;fill-rule:nonzero;stroke:#1a1a1a;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" d="m 541.2398,608.99416 c 1e-5,12.28836 -9.96168,22.25005 -22.25005,22.25004 -12.28836,10e-6 -22.25004,-9.96168 -22.25004,-22.25004 10e-6,-12.28836 9.96169,-22.25002 22.25004,-22.25002 12.28836,0 22.25005,9.96166 22.25005,22.25002 z"/>
   <path class="static" style="fill:#0000ff;fill-opacity:1;stroke:#1a1a1a;stroke-width:2.73460841;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 394.64632,689.14759 -27.8083,-28.0013 m 27.90481,0.0965 -28.0013,27.80834 M 402.37488,675.039 c 0,11.94741 -9.68528,21.6327 -21.63269,21.6327 -11.94742,10e-6 -21.63271,-9.68528 -21.63271,-21.6327 0,-11.94741 9.6853,-21.6327 21.63271,-21.63269 11.9474,0 21.63269,9.68529 21.63269,21.63269 z"/>
</svg>
</div>
</body></html>

I need integrate this with jQuery and fabric as the rest of the project use those libraries.

Also need that the triggering of the element to pop up would be mouse click/touch-screen events.

The element should fill the whole superior half of the screen no matter the position it was before and after edited return to the exact same position on another mouse/touch event, or maybe via a close/save button appended to the pop up.

The svg objects on the inferior part are going to act as menu items. I need at least the red and another in blue to act as color pickers where the user touch one of them and then apply that color to the editable items above in the screen once they are poped up to be edited.

There will be a lot of this items on the canvas. If I just placed two of them here was for the obvious reason.

I’ve added more code from Peter Collingridge’s tutorial about draggable svg elements.

If someone knows how to solve it, or at least a part of it, I will be very thankfull.


Get this bounty!!!

#StackBounty: #javascript #html #jquery #css #html2canvas How to download customisable color change box as image with the background im…

Bounty: 50

I have a box that can change color once you select the box and then select the color.

However, there is also a background image that shows the outline of the box. I have used HTML2canvas and jquery as libraries so I can download the customise color change box as an image, but it could only be downloaded locally if the image is hidden – as shown on the CSS.

My question is, how do I download the customisable color change box with the background outline box image? – or – Is there an alternative way to save the background image and the SVG locally with download button?

//////// carousel ////////

let sliderImages = document.querySelectorAll('.slide'),
  arrowLeft = document.querySelector('#arrow-left'),
  arrowRight = document.querySelector('#arrow-right'),
  current = 0;

//Clear all images
function reset() {
  for (let i = 0; i < sliderImages.length; i++) {
    sliderImages[i].style.display = 'none';
  }
}
// initialize slider
function startSlide() {
  reset();
  sliderImages[0].style.display = "block";
}

//show previous
function slideLeft() {
  reset();
  sliderImages[current - 1].style.display = "block";
  current--;
}

//show next
function slideRight() {
  reset();
  sliderImages[current + 1].style.display = "block";
  current++;
}

//left arrow click
arrowLeft.addEventListener('click', function() {
  if (current === 0) {
    current = sliderImages.length;
  }
  slideLeft();
});

//right arrow click
arrowRight.addEventListener('click', function() {
  if (current === sliderImages.length - 1) {
    current = -1;
  }
  slideRight();
});

startSlide();

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'));
}

//////// remove highlight when clicking outside of image ////////
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;

//////// remove highlight of a specific shape ////////
function chooseProduct(e) {
  for (let i = 0; i < overlays.length; i += 1) {
    let currentOverlay = overlays[i];
    if (currentOverlay.isSameNode(e.target)) {
      overlays.splice(i, 1);
      e.target.classList.remove('highlight')
      return;
    }
  }
  overlays.push(e.target);
  overlays.forEach((overlay) => overlay.classList.add("highlight"));
}

//////// get and set colours ////////

// Click on a color
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");
  // set the hex color
  overlays.forEach((overlay) => overlay.style.fill = hex);
}

$(document).ready(function() {
  function saveScreenshot(canvas) {
    var downloadLink = document.createElement('a');
    downloadLink.download = 'download.jpg';
    canvas.toBlob(function(blob) {
      downloadLink.href = URL.createObjectURL(blob)
      downloadLink.click();
    });
  }

  $(".download-btn").on("click", function(e) {
    e.preventDefault();
    html2canvas(document.querySelector(".download-container"), {
      scrollX: 0,
      scrollY: 0
    }).then(function(canvas) {
      var image = canvas.toDataURL('image/jpeg');
      document.getElementById("created-element").src = image;
      $(this).attr('href', image);
      saveScreenshot(canvas);
    });
  });
});
.grid-container {
  display: grid;
  grid-template-columns: auto 5% 1fr auto 1fr;
  grid-template-rows: 128px auto 1fr auto auto auto auto 100px;
  gap: 0px 0px;
  grid-auto-flow: row;
  grid-template-areas: 
    "header . . . ." 
    "main main main color-select color-select" 
    "main main main color-select color-select" 
    "about about about about about" 
    "howto howto howto howto howto" 
    "faqs faqs faqs faqs faqs" 
    "social social social social social" 
    "footer footer footer footer footer";
}

.header {
  grid-area: header;
}

.logo {
  display: inline-block;
  padding-top: 20px;
  padding-left: 65px;
}

.navbar {
  display: inline-block;
  padding-top: 50px;
  padding-right: 20px;
  font-family: 'Roboto Condensed', sans-serif;
  line-height: 38px;
  font-weight: 400;
  font-size: 18px;
  float: right;
}

.nav-link {
  margin: 18px;
  color: #212529;
  text-decoration: none;
}

.main {
  grid-area: main;
  background-color: #f8f8f8;
  padding-top: 20px;
  padding-bottom: 50px;
  display: flex;
  text-align: center;
  position: relative;
  margin-top: 2.5px;
  margin-left: 78px;
}

#slider,
.wrap,
.slide-content {
  max-width: 1000px;
  position: relative;
  margin: auto;
}

.slide-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.arrow {
  cursor: pointer;
  position: absolute;
  top: 101%;
  width: 60%;
  height: 0;
  z-index: 1;
  font-size: 20px;
  color: #cccccc;
}

#arrow-left {
  left: 0;
}

#arrow-right {
  right: 0;
}


/* Caption text */

.text {
  position: relative;
  color: #212529;
  font-size: 18px;
  top: 28px;
  width: 100%;
  text-align: center;
  font-family: 'Roboto Condensed', sans-serif;
  font-weight: 400;
}

.lion-number {
  color: #8f8f8f;
}

.color-select {
  display: flex;
  align-items: center;
  grid-area: color-select;
  background-color: #f8f8f8;
  margin-top: 2.5px;
  margin-right: 78px;
  padding: 10px;
}

body,
html {
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
}

#container {
  width: 100%;
  height: auto;
}

#product-svg {
  position: absolute;
  z-index: 2;
  background-size: 100%;
  background-repeat: no-repeat;
  background-position: 50%;
  mix-blend-mode: multiply;
  width: 85%;
  height: auto;
}

path {
  fill: #8f8f8f;
}

.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;
}

img {
  visibility: hidden;
}

button {
  font-size: 1.25em;
  padding: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js" integrity="sha512-n/4gHW3atM3QqRcbCn6ewmpxcLAHGaDjpEBu4xZd47N0W2oQ+6q7oc3PXstrJYXcbNU1OHdQ1T7pAP+gi5Yu8g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.2.2/html2canvas.js" integrity="sha512-Alb3nvf6wRVUhs6H8foMA8fuWAKFFretiYkk2WbrMSbAtTtNBOjKLbDIagmFVypIi4wT1pRhHwz+R/W7nU31wg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div class="grid-container">
  <header class="header">
  
    <img class="logo" src="logo.png" alt="">

    <nav class="navbar">
      <a href=about.html class="nav-link">About</a>
      <a href=howto.html class="nav-link">How to</a>
      <a href=faqs.html class="nav-link">FAQs</a>
      <a href=contact.html class="nav-link">Contact</a>
    </nav>

  </header>
  
  
  
</div>
</div>



  <main class="main">

    <div class="wrap">
      <div id="arrow-left" class="arrow"><span>❮</span></div>
      <div id="arrow-right" class="arrow"><span>❯</span></div>
      <div id="slider">

        <div class="slide slide1">
          <div class="slide-content">
            <div id="container">
              <div class="download-container">
                <svg id="product-svg" viewBox="0 0 256 256">
                  <path id="box1" class="product" d="M24 130.25L24 235L233 235L233 25.5L24 25.5L24 130.25Z" />
                </svg>

                <img class="background-image" src="https://images.vexels.com/media/users/3/139342/isolated/lists/61cddf9cfe50f4baaa8f472c253d1cb4-basic-square-outline.png" alt="">
              </div>
            </div>

            <div class="text">image1</div>
          </div>
        </div>

        <div class="slide slide1">
          <div class="slide-content"
            <div id="container">
              <div class="download-container">
                <svg id="product-svg" viewBox="0 0 256 256">
                  <path id="box2" class="product" d="M24 130.25L24 235L233 235L233 25.5L24 25.5L24 130.25Z" />
                </svg>

                <img class="background-image" src="https://images.vexels.com/media/users/3/139342/isolated/lists/61cddf9cfe50f4baaa8f472c253d1cb4-basic-square-outline.png" alt="">
              </div>
            </div>

            <div class="text">image2</div>
          </div>
        </div>

        <button class="download-btn">Download element!</button>
        <img src="" id="created-element" />

  </main>

  <section class="color-select">
    <div data-test>
      <span class="color red">
        <span class="color-selected" style="--color: #ff6666 " data-hex="#ff6666"></span>
      </span>
    </div>
  </section>

</div>


Get this bounty!!!