>  기사  >  웹 프론트엔드  >  프런트엔드 발전(10): 드래그 앤 드롭 개체 캡슐화

프런트엔드 발전(10): 드래그 앤 드롭 개체 캡슐화

PHPz
PHPz원래의
2017-04-04 17:57:401397검색

프런트엔드 발전(10): 드래그 앤 드롭 개체 캡슐화

드디어

이전 기사에서는 JavaScript에 대한 기본 지식을 알려 드렸습니다. 이번 기사에서는 첫 번째 실습에 들어갑니다. 링크: 이전 장의 지식을 사용하여 드래그 객체 를 캡슐화합니다. 더 많은 방법에 대한 이해와 비교를 돕기 위해 세 가지 방법을 사용하여 드래그 앤 드롭을 구현하겠습니다.

  • 객체를 캡슐화하지 않고 직접 구현

  • 기본 JavaScript를 사용하여 드래그 앤 드롭 객체를 캡슐화합니다. 🎜> 드래그 앤 드롭 객체는

    jQuery
  • 를 확장하여 구현됩니다.
  • 이 글의 예제는 모든 사람이 읽으면서 직접 볼 수 있도록 codepen.io에 배치됩니다. codepen에 대해 모르신다면 시간을 내어 조금 배워보시면 됩니다.
드래그 앤 드롭의 구현 과정에는 많은 실용적인 지식이 포함되므로, 내가 축적한 지식을 공고히 하고 모든 사람이 더 많은 지식을 배울 수 있도록 최선을 다해 설명하겠습니다. 세부 사항 일부 세부 사항은 공유됩니다. 모든 사람이 주의 깊게 읽은 후에 뭔가를 배울 것이라고 믿습니다.

1. DOM 요소를 이동시키는 방법

우리는 종종 요소의 <a href="http://www.php.cn/wiki/904.html" target="_blank">상단</a>

, 왼쪽, 번역 및 위치가 변경됩니다. 아래 예시에서는
버튼
을 클릭할 때마다 해당 요소가 5px씩 이동합니다. 클릭하시면 보실 수 있습니다.

<a href="http://www.php.cn/wiki/904.html" target="_blank">top</a>,left,translate요소를 애니메이션으로 만드는 간단한 예를 보려면 클릭하세요

요소의 상단/왼쪽 값을 수정하면 페이지가 다시 그려지지만 번역은 그렇지 않으므로 from

성능

의 최적화로 판단하여
번역 속성

을 우선적으로 사용하도록 하겠습니다. 2. 현재 브라우저

에서 지원하는
m 호환 쓰기 방법을 얻는 방법
변환은 css3의 속성이며, 우리는 직면해야 합니다. 우리가 그것을 사용할 때 호환성 문제에 관해. 다양한 버전의 브라우저에서 호환되는 작성 방법은 대략 다음과 같습니다.

따라서 현재 브라우저 환경에서 어떤 변환 속성을 지원하는지 확인해야 합니다. 다음은 다음과 같습니다. ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform']

// 获取当前浏览器支持的transform兼容写法
function getTransform() {
    var transform = '',
        pStyle = document.createElement('p').style,
        // 可能涉及到的几种兼容性写法,通过循环找出浏览器识别的那一个
        transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'],

        i = 0,
        len = transformArr.length;

    for(; i < len; i++)  {
        if(transformArr[i] in pStyle) {
            // 找到之后立即返回,结束函数
            return transform = transformArr[i];
        }
    }

    // 如果没有找到,就直接返回空字符串
    return transform;
}
이 메소드는 브라우저에서 지원하는 변환 속성을 얻는 데 사용됩니다. 반환된 문자열이 빈 문자열이면 현재 브라우저가 변환을 지원하지 않는다는 의미입니다. 이때 요소의 위치를 ​​변경하려면 왼쪽 및 위쪽 값을 사용해야 합니다. 지원되는 경우 변환 값을 변경하십시오.

3. 요소의 초기 위치를 가져오는 방법

먼저 대상 요소의 초기 위치를 가져와야 하므로 여기서는 요소 스타일을 가져오는 데 특별히 사용되는 함수가 필요합니다.
그러나

IE 브라우저

에서 요소 스타일을 얻는 것은 다른 브라우저와 다소 다르기 때문에 호환 가능한 작성 방법이 필요합니다.

function getStyle(elem, property) {
    // ie通过currentStyle来获取元素的样式,其他浏览器通过getComputedStyle来获取
    return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(elem, false)[property] : elem.currentStyle[property];
}
이 메서드를 사용한 후에는 대상 요소의 초기 위치를 가져오는 메서드 작성을 시작할 수 있습니다.
function getTargetPos(elem) {
    var pos = {x: 0, y: 0};
    var transform = getTransform();
    if(transform) {
        var transformValue = getStyle(elem, transform);
        if(transformValue == 'none') {
            elem.style[transform] = 'translate(0, 0)';
            return pos;
        } else {
            var temp = transformValue.match(/-?\d+/g);
            return pos = {
                x: parseInt(temp[4].trim()),
                y: parseInt(temp[5].trim())
            }
        }
    } else {
        if(getStyle(elem, 'position') == 'static') {
            elem.style.position = 'relative';
            return pos;
        } else {
            var x = parseInt(getStyle(elem, 'left') ? getStyle(elem, 'left') : 0);
            var y = parseInt(getStyle(elem, 'top') ? getStyle(elem, 'top') : 0);
            return pos = {
                x: x,
                y: y
            }
        }
    }
}
드래그 과정에서 대상 요소가 이동하려면 지속적으로 대상 요소의 새로운 위치를 설정해야 하므로 대상 요소의 위치를 ​​설정하는 방법이 필요합니다.

아아아

5. 어떤

이벤트

를 사용해야 하나요?
PC의 브라우저에서 의 세 가지 이벤트를 결합하면 드래그 앤 드롭을 구현하는 데 도움이 될 수 있습니다.

mousedown、mousemove、mouseup

    마우스를 눌렀을 때 발생
  • mousedown

  • 마우스를 누른 후 드래그할 때 발생
  • mousemove

  • 마우스를 놓았을 때 발생
  • mouseup

  • 모바일 측에서는 해당 touchstart, touchmove, touch<a href="http://www.php.cn/wiki/1048.html" target="_blank">끝</a>
.

touchstart、touchmove、touch<a href="http://www.php.cn/wiki/1048.html" target="_blank">end</a>이러한 이벤트를 요소에 바인딩하면 이벤트 개체가

콜백 함수
에 매개변수로 전달됩니다. 이벤트 개체를 통해 이벤트의 정확한 위치를 얻을 수 있습니다. 현재 마우스 위치, 마우스 위치 정보는 드래그 앤 드롭을 구현하는 데 핵심입니다.

이벤트 개체에는 유용한 정보가 많이 포함되어 있습니다. 여기서는 이벤트 개체를 인쇄하여 특정 속성을 볼 수 있습니다. 이벤트 객체의 중요한 속성을 기억하지 못하는 어린이에게 매우 유용합니다.

6、拖拽的原理

当事件触发时,我们可以通过事件对象获取到鼠标的精切位置。这是实现拖拽的关键。当鼠标按下(mousedown触发)时,我们需要记住鼠标的初始位置与目标元素的初始位置,我们的目标就是实现当鼠标移动时,目标元素也跟着移动,根据常理我们可以得出如下关系:

移动后的鼠标位置 - 鼠标初始位置 = 移动后的目标元素位置 - 目标元素的初始位置

如果鼠标位置的差值我们用dis来表示,那么目标元素的位置就等于:

移动后目标元素的位置 = dis + 目标元素的初始位置

通过事件对象,我们可以精确的知道鼠标的当前位置,因此当鼠标拖动(mousemove)时,我们可以不停的计算出鼠标移动的差值,以此来求出目标元素的当前位置。这个过程,就实现了拖拽。

而在鼠标松开(mouseup)结束拖拽时,我们需要处理一些收尾工作。详情见代码。

7、 我又来推荐思维导图辅助写代码了

常常有新人朋友跑来问我,如果逻辑思维能力不强,能不能写代码做前端。我的答案是:能。因为借助思维导图,可以很轻松的弥补逻辑的短板。而且比在自己头脑中脑补逻辑更加清晰明了,不易出错。

上面第六点我介绍了原理,因此如何做就显得不是那么难了,而具体的步骤,则在下面的思维导图中明确给出,我们只需要按照这个步骤来写代码即可,试试看,一定很轻松。

프런트엔드 발전(10): 드래그 앤 드롭 개체 캡슐화

使用思维导图清晰的表达出整个拖拽过程我们需要干的事情

8、代码实现

part1、准备工作

// 获取目标元素对象
var oElem = document.getElementById('target');

// 声明2个变量用来保存鼠标初始位置的x,y坐标
var startX = 0;
var startY = 0;

// 声明2个变量用来保存目标元素初始位置的x,y坐标
var sourceX = 0;
var sourceY = 0;

part2、功能函数

因为之前已经贴过代码,就不再重复

// 获取当前浏览器支持的transform兼容写法
function getTransform() {}

// 获取元素属性
function getStyle(elem, property) {}

// 获取元素的初始位置
function getTargetPos(elem) {}

// 设置元素的初始位置
function setTargetPos(elem, potions) {}

part3、声明三个事件的回调函数

这三个方法就是实现拖拽的核心所在,我将严格按照上面思维导图中的步骤来完成我们的代码。

// 绑定在mousedown上的回调,event为传入的事件对象
function start(event) {
    // 获取鼠标初始位置
    startX = event.pageX;
    startY = event.pageY;

    // 获取元素初始位置
    var pos = getTargetPos(oElem);

    sourceX = pos.x;
    sourceY = pos.y;

    // 绑定
    document.addEventListener('mousemove', move, false);
    document.addEventListener('mouseup', end, false);
}

function move(event) {
    // 获取鼠标当前位置
    var currentX = event.pageX;
    var currentY = event.pageY;

    // 计算差值
    var distanceX = currentX - startX;
    var distanceY = currentY - startY;

    // 计算并设置元素当前位置
    setTargetPos(oElem, {
        x: (sourceX + distanceX).toFixed(),
        y: (sourceY + distanceY).toFixed()
    })
}

function end(event) {
    document.removeEventListener('mousemove', move);
    document.removeEventListener('mouseup', end);
    // do other things
}

OK,一个简单的拖拽,就这样愉快的实现了。点击下面的链接,可以在线查看该例子的demo。

使用原生js实现拖拽

9、封装拖拽对象

在前面一章我给大家分享了面向对象如何实现,基于那些基础知识,我们来将上面实现的拖拽封装为一个拖拽对象。我们的目标是,只要我们声明一个拖拽实例,那么传入的目标元素将自动具备可以被拖拽的功能。

在实际开发中,一个对象我们常常会单独放在一个js文件中,这个js文件将单独作为一个模块,利用各种模块的方式组织起来使用。当然这里没有复杂的模块交互,因为这个例子,我们只需要一个模块即可。

为了避免变量污染,我们需要将模块放置于一个函数自执行方式模拟的块级作用域中。

;
(function() {
    ...
})();

在普通的模块组织中,我们只是单纯的将许多js文件压缩成为一个js文件,因此此处的第一个分号则是为了防止上一个模块的结尾不用分号导致报错。必不可少。当然在通过require或者ES6模块等方式就不会出现这样的情况。

我们知道,在封装一个对象的时候,我们可以将属性与方法放置于构造函数或者原型中,而在增加了自执行函数之后,我们又可以将属性和方法防止与模块的内部作用域。这是闭包的知识。

那么我们面临的挑战就在于,如何合理的处理属性与方法的位置。

当然,每一个对象的情况都不一样,不能一概而论,我们需要清晰的知道这三种位置的特性才能做出最适合的决定。

  • 构造函数中: 属性与方法为当前实例单独拥有,只能被当前实例访问,并且每声明一个实例,其中的方法都会被重新创建一次。

  • 原型中: 属性与方法为所有实例共同拥有,可以被所有实例访问,新声明实例不会重复创建方法。

  • 模块作用域中:属性和方法不能被任何实例访问,但是能被内部方法访问,新声明的实例,不会重复创建相同的方法。

对于方法的判断比较简单。

因为在构造函数中的方法总会在声明一个新的实例时被重复创建,因此我们声明的方法都尽量避免出现在构造函数中。

而如果你的方法中需要用到构造函数中的变量,或者想要公开,那就需要放在原型中。

如果方法需要私有不被外界访问,那么就放置在模块作用域中。

对于属性放置于什么位置有的时候很难做出正确的判断,因此我很难给出一个准确的定义告诉你什么属性一定要放在什么位置,这需要在实际开发中不断的总结经验。但是总的来说,仍然要结合这三个位置的特性来做出最合适的判断。

如果属性值只能被实例单独拥有,比如person对象的name,只能属于某一个person实例,又比如这里拖拽对象中,某一个元素的初始位置,也仅仅只是这个元素的当前位置,这个属性,则适合放在构造函数中。

而如果一个属性仅仅供内部方法访问,这个属性就适合放在模块作用域中。

关于面向对象,上面的几点思考我认为是这篇文章最值得认真思考的精华。如果在封装时没有思考清楚,很可能会遇到很多你意想不到的bug,所以建议大家结合自己的开发经验,多多思考,总结出自己的观点。

根据这些思考,大家可以自己尝试封装一下。然后与我的做一些对比,看看我们的想法有什么不同,在下面例子的注释中,我将自己的想法表达出来。

点击查看已经封装好的demo

js 源码

;
(function() {
    // 这是一个私有属性,不需要被实例访问
    var transform = getTransform();

    function Drag(selector) {
        // 放在构造函数中的属性,都是属于每一个实例单独拥有
        this.elem = typeof selector == 'Object' ? selector : document.getElementById(selector);
        this.startX = 0;
        this.startY = 0;
        this.sourceX = 0;
        this.sourceY = 0;

        this.init();
    }


    // 原型
    Drag.prototype = {
        constructor: Drag,

        init: function() {
            // 初始时需要做些什么事情
            this.setDrag();
        },

        // 稍作改造,仅用于获取当前元素的属性,类似于getName
        getStyle: function(property) {
            return document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(this.elem, false)[property] : this.elem.currentStyle[property];
        },

        // 用来获取当前元素的位置信息,注意与之前的不同之处
        getPosition: function() {
            var pos = {x: 0, y: 0};
            if(transform) {
                var transformValue = this.getStyle(transform);
                if(transformValue == 'none') {
                    this.elem.style[transform] = 'translate(0, 0)';
                } else {
                    var temp = transformValue.match(/-?\d+/g);
                    pos = {
                        x: parseInt(temp[4].trim()),
                        y: parseInt(temp[5].trim())
                    }
                }
            } else {
                if(this.getStyle('position') == 'static') {
                    this.elem.style.position = 'relative';
                } else {
                    pos = {
                        x: parseInt(this.getStyle('left') ? this.getStyle('left') : 0),
                        y: parseInt(this.getStyle('top') ? this.getStyle('top') : 0)
                    }
                }
            }

            return pos;
        },

        // 用来设置当前元素的位置
        setPostion: function(pos) {
            if(transform) {
                this.elem.style[transform] = 'translate('+ pos.x +'px, '+ pos.y +'px)';
            } else {
                this.elem.style.left = pos.x + 'px';
                this.elem.style.top = pos.y + 'px';
            }
        },

        // 该方法用来绑定事件
        setDrag: function() {
            var self = this;
            this.elem.addEventListener('mousedown', start, false);
            function start(event) {
                self.startX = event.pageX;
                self.startY = event.pageY;

                var pos = self.getPosition();

                self.sourceX = pos.x;
                self.sourceY = pos.y;

                document.addEventListener('mousemove', move, false);
                document.addEventListener('mouseup', end, false);
            }

            function move(event) {
                var currentX = event.pageX;
                var currentY = event.pageY;

                var distanceX = currentX - self.startX;
                var distanceY = currentY - self.startY;

                self.setPostion({
                    x: (self.sourceX + distanceX).toFixed(),
                    y: (self.sourceY + distanceY).toFixed()
                })
            }

            function end(event) {
                document.removeEventListener('mousemove', move);
                document.removeEventListener('mouseup', end);
                // do other things
            }
        }
    }

    // 私有方法,仅仅用来获取transform的兼容写法
    function getTransform() {
        var transform = '',
            pStyle = document.createElement('p').style,
            transformArr = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'OTransform'],

            i = 0,
            len = transformArr.length;

        for(; i < len; i++)  {
            if(transformArr[i] in pStyle) {
                return transform = transformArr[i];
            }
        }

        return transform;
    }

    // 一种对外暴露的方式
    window.Drag = Drag;
})();

// 使用:声明2个拖拽实例
new Drag('target');
new Drag('target2');

这样一个拖拽对象就封装完毕了。

建议大家根据我提供的思维方式,多多尝试封装一些组件。比如封装一个弹窗,封装一个循环轮播等。练得多了,面向对象就不再是问题了。这种思维方式,在未来任何时候都是能够用到的。

위 내용은 프런트엔드 발전(10): 드래그 앤 드롭 개체 캡슐화의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.