Javascript drag event on an element is not triggering on first drag

Viewed 21

I'm experimenting with custom HTML elements in HTML and JS. Making several custom elements like Select, Number input and so on.

Right now i want to create a shader / shutter slider from scratch. It is because the default range sliders are ***** and they create more and more problems as i try to rotate and do math with them on various browsers.

So my problem is, I'm using drag event to determine if the user is dragging the slider's handle or not. And if it is dragged i compute where it is on the slider and update the percentages and pixels accordingly. When i click on the handle to drag it the drag event does not fire at first and i have to click away and try to drag it again.

Here is the code on codepen: https://codepen.io/drrandom/pen/Vwxmrrr

If you don't want to check it on codepen I have included a snippet in here too.

class hsShader extends HTMLElement {
    // <span class="hs-shader-percent-indicator show">50%</span>
    template = `
        <span class="hs-shader-upper"></span>
        <span class="hs-shader-handle">
            <span class="hs-shader-percent-holder">50%</span>
        </span>`;
    percent = 50;
    handleHeight = 10;

    handleEl = null;
    //indicatorEl = null;
    upperHalfEl = null;
    connectedCallback() {
        this.id = this.getAttribute("id");
        if (!this.id || this.id === "") {
            console.error("[HSH-Shader] - Shader should have an ID.");
            return;
        }

        this.addTemplate();
        this.getElements();
        this.getAttributes();
        this.addEvents();
    }

    getElements() {
        this.handleEl           = this.querySelector(".hs-shader-handle");
        //this.indicatorEl = this.querySelector(".hs-shader-percent-indicator");
        this.upperHalfEl        = this.querySelector(".hs-shader-upper");
        this.percentHolderEl    = this.querySelector(".hs-shader-percent-holder");
        this.handleHeight       = this.handleEl.offsetHeight;
    }

    getAttributes() {
        this.percent = parseInt(this.getAttribute("percent"));
        this.refreshDisplayPercent();
    }

    addTemplate() {
        this.innerHTML = this.template;
    }

    addEvents() {
        this.handleDrag();
        //this.handleClick();
    }
    
    handleClick(){
        let self = this;
        ['mouseup', 'touchend'].forEach(evt =>
            self.addEventListener(evt, function (e) {
                let relativeTop = 0;
                const rect = self.getBoundingClientRect();
                if( e.type == "mouseup" ){
                    relativeTop = e.clientY - rect.top;
                }else{
                    relativeTop = e.touches[0].pageY - rect.top
                }
                let percent = self.getPosPercent(relativeTop);
                self.setPercent( percent );
                return false;
            }, false)
        );
    }
  
    handleDrag() {
        let self = this;
        ['drag', 'touchmove'].forEach(evt =>
            self.handleEl.addEventListener(evt, function (e) {
                const target = e.target.parentElement;
                const rect = target.getBoundingClientRect();
                let relativeTop;
                if( e.type == "drag" ){
                    relativeTop = e.clientY - rect.top;
                }else{
                    relativeTop = e.touches[0].pageY - rect.top
                }
                self.updateDisplayPixel(relativeTop);
                return false;
            }, false)
        );

        ['dragstart', 'touchstart', "click"].forEach(evt =>
            self.handleEl.addEventListener(evt, function (e) {
                //self.indicatorEl.classList.add("show");
                //e.dataTransfer.setData('application/node type', this);
                window.getSelection().removeAllRanges();
                return false;
            }, false)
        );
        // ['dragend', 'touchend', "touchcancel"].forEach(evt =>
        //     self.handleEl.addEventListener(evt, function (e) {
        //         self.indicatorEl.classList.remove("show");
        //     }, false)
        // );
    }

    getPosPixel() {
        let height = this.offsetHeight;
        return this.clamp( ((height / 100) * this.percent) - (this.handleHeight / 2) );
    }

    getPosPercent(pixel) {
        return this.clamp( Math.round( (100 / this.offsetHeight) * pixel) );
    }

    refreshDisplayPercent() {
        this.upperHalfEl.style.height   = `${this.percent}%`;
        this.handleEl.style.top         = `${this.getPosPixel()}px`;
        this.percentHolderEl.innerText  = `${this.percent}%`;
        //this.indicatorEl.innerHTML      = `${this.percent}%`;
    }

    updateDisplayPixel(pixel) {
        if (pixel < 0 || pixel > this.offsetHeight) { return; }
        let percent = this.getPosPercent(pixel);
        let posPercentString = `${percent}%`
        this.upperHalfEl.style.height   = `${pixel}px`;
        this.handleEl.style.top         = `${pixel - (this.handleHeight / 2)}px`;
        //this.indicatorEl.innerHTML      = posPercentString;
        this.percentHolderEl.innerText  = posPercentString;
        this.percent = percent;
    }

    clamp(val, min, max) {
        return val > max ? max : val < min ? min : val;
    }

    setPercent(percent) {
        if (percent > 100 || percent < 0) {
            console.error("[HSH-Shader] - Percent must be between 0 and 100%!");
        }
        this.percent = this.clamp(percent,0,100);
        this.refreshDisplayPercent();
    }
};
customElements.define('hs-shader', hsShader);
body{
  width: 100%;
  height: 100vh;
  
  display: flex;
  flex-direction: column;
  justify-content:center;
  text-align:center;
}

.shader-container{
  display: flex;
  flex-direction: row;
  gap: 100px;
  justify-content:center;
  align-items:center;
}

:root{
    --shader_top_half_color: cornflowerblue;
    --shader_thumb_color: #5e7cd3;
    
    --shader_top_half_color_animate: coral;
    --shader_thumb_color_animate: #ce5d33;
  
    --group_shader_top_half_color: #4bcca1;
    --group_shader_thumb_color: #4cbb9f;
}

hs-shader{
  position:relative;
  width: 100px;
  height: 150px;
  border-radius: 0.3em;
  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
  
  display: flex;
  flex-direction: column;
}

.hs-shader-upper{
  width: 100%;
  height: 50%;
  background-color: var(--shader_top_half_color);
  border-radius: 0.3em;
}

.hs-shader-handle{
  position: absolute;
  left: -5px;
  top: calc(50% - 10px);
  
  display: flex;
  justify-content:center;
  align-items:center;
  
  width: 110%;
  height: 20px;
  border-radius: 0.4em;
  background-color: var(--shader_thumb_color);
  color: white;
  
  cursor: pointer;
}

.hs-shader-percent-indicator{
  display: none;
}

.hs-shader-percent-indicator.show{
  position:absolute;
  right: -70px;
  display: flex;
  justify-content:center;
  align-items:center;
  font-weight: bold;
  font-size: 1em;
  opacity: 0;
  animation: hs-shader-show-indicator 0.3s ease-in forwards;
  width: 50px;
  height: 40px;
  background: cornflowerblue;
  -moz-border-radius: 10px;
  -webkit-border-radius: 10px;
  border-radius: 10px;
  box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
}

@keyframes hs-shader-show-indicator{
  0%{
    opacity: 0;
  }  
  100%{
    opacity: 1;
  }
}

.hs-shader-percent-indicator::before{
  content: "";
  position: absolute;
  right: 100%;
  top: 10px;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-right: 20px solid cornflowerblue;
  border-bottom: 10px solid transparent;
}

.hs-shader-handle:hover{
  box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px, rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;
}
<<h1>As you can see the drag event does not fire on first drag</h1>
<div class="shader-container">
    <hs-shader id="test" percent="50"></hs-shader>
    <hs-shader id="test2" percent="30"></hs-shader>
</div>

0 Answers
Related