An elegant small drag sorting program component implementation

Foreword

Recently po main write a small program encountered during a drag sorting needs. Internet search unsuccessful meal, then self-fulfilling.

This is not the renderings, the scan code direct experience of it.

inspiration

First, as no ready-made small so what program I refer to the case, so a little impossible to start, find a reference to achieve h5 drag reference. So in jquery plug-in network saw a few drag sorted to determine the basic idea. probably to do with the transform transformation. Yes, this kind of thing is to draw inspiration came ~~

Identify needs

  1. To be able to drag and drop, drag and drop, after all, the sort Well, certainly the first drag.
  2. To be able to sort, first, the day after wandering ~~ drag, drag over or else sure to be sorted and the movable-view so what difference does it make.
  3. Since the number of columns can be defined in order to achieve different forms of presentation, we consider here the number of columns have different needs in different situations of the list is sorted, sorting photo albums and other needs.
  4. No bug, uh uh uh, I'll try this.

Realization of ideas

First of all can drag and drop elements must be at least the same size, as not unusual size, or the size of a multiple relationships achieved in this range.

Then we find the corresponding demand solutions:

Drag achieve

  1. Use realize drag movable-view, this approach is simple and quick, but as our inspiration to do is to use the transform transform, and here with the movable-view transform itself to achieve, so there will be conflict, then discarded.

  2. Use custom gestures, such as touchstart, touchmove, touchend. This is for the three gay, although we do use the drop-down refresh when using the movable-view and abandoned the three brothers, but gold will shine today that is, you have three brothers show their skills when the (really fragrant warning). nonsense bit more, get down to business, using a custom gestures can help us to control every detail, fly in the ointment is that their father did not offer a manual stop characteristics bubbling, engage had a catch and bind event, then do not support dynamic switching, so pit father, but this is not a problem, it is to pit some Bale.

Sort achieve

Ranking is based on the drag by the above touchstart, touchmove, touchend three brothers dynamically calculated position of the current element sorted get the touch information, and the dynamic position to other elements within the array element replacement sorted according to the currently active position. Probably meaning a row of ten brothers do, the boss up position went to the youngest, youngest looked forward shift of the shift, the second also looked forward shift of the shift. of course, this is positive, as well as reverse, For example, the old ten went to the boss position, then after the boss to the old nine had to order up one position.

Since the number of columns defined

Defined number columns, the difficulty is nothing, a small program components exposed column attributes, then the fixed number of columns in the calculation process parameters can be changed to a

Achieve analysis

First on touchstart, touchmove, touchend three brothers

longPress

Here to experience the touchstart replaced longpress press the trigger. First we need to set up a state in dragging the touch means we then get is pageX, pageY attention here for pageX, pageY instead clientX, clientY because we have drag components there may be still a top margin or other elements, this time if the acquisition clientX, clientY there will be a deviation. here the current pageX, pageY set as the initial touch point startX, startY.

Then calculate the required offset position and tranY the Initialization tranX activation element, in order to optimize the experience where initialization tranX not displaced when the number of columns 1, tranY moved to an intermediate position currently active element, when the plurality of rows and tranY tranX all currently active elements is displaced to an intermediate position.

Finally set the currently active index of the element cur and offset tranX, tranY. Then shake it down wx.vibrateShort () Experience Mei Mei Da.

/**
 * 长按触发移动排序
 */
longPress(e) {
    this.setData({
        touch: true
    });

    this.startX = e.changedTouches[0].pageX
    this.startY = e.changedTouches[0].pageY

    let index = e.currentTarget.dataset.index;

    if(this.data.columns === 1) { // 单列时候X轴初始不做位移
        this.tranX = 0;
    } else {  // 多列的时候计算X轴初始位移, 使 item 水平中心移动到点击处
        this.tranX = this.startX - this.item.width / 2 - this.itemWrap.left;
    }

    // 计算Y轴初始位移, 使 item 垂直中心移动到点击处
    this.tranY = this.startY - this.item.height / 2 - this.itemWrap.top;

    this.setData({
        cur: index,
        tranX: this.tranX,
        tranY: this.tranY,
    });

    wx.vibrateShort();
}

touchMove

touchmove every time the protagonist of the story, and this time is no exception. Look at this full amount of code to know. First came the need to determine whether the drag, rather than the need to return.

And then determine whether more than one screen, this is what it means, because we drag element may be a lot even more than the entire screen, slide need to deal with here, but we use the catch:... Touchmove event it will clog page slide so we need processed in the elements when more than one screen, there are two cases. one is that we drag elements to the bottom of the page when the page automatically scrolls down from the height of an element, the other element is dragged to the top of the page when the time Auto scroll up a page element height distance.

Then we set has been re-calculated tranX and tranY, and obtain the sort key key of the current element as the initial originKey, then calculate endKey by the current tranX and tranY use calculateMoving method.

Finally, we call this.insert (originKey, endKey) method to sort the array

touchMove(e) {
    if (!this.data.touch) return;
    let tranX = e.touches[0].pageX - this.startX + this.tranX,
        tranY = e.touches[0].pageY - this.startY + this.tranY;

    let overOnePage = this.data.overOnePage;

    // 判断是否超过一屏幕, 超过则需要判断当前位置动态滚动page的位置
    if(overOnePage) {
        if(e.touches[0].clientY > this.windowHeight - this.item.height) {
            wx.pageScrollTo({
                scrollTop: e.touches[0].pageY + this.item.height - this.windowHeight,
                duration: 300
            });
        } else if(e.touches[0].clientY < this.item.height) {
            wx.pageScrollTo({
                scrollTop: e.touches[0].pageY - this.item.height,
                duration: 300
            });
        }
    }

    this.setData({tranX: tranX, tranY: tranY});

    let originKey = e.currentTarget.dataset.key;

    let endKey = this.calculateMoving(tranX, tranY);

    // 防止拖拽过程中发生乱序问题
    if (originKey == endKey || this.originKey == originKey) return;

    this.originKey = originKey;

    this.insert(originKey, endKey);
}

calculateMoving 方法

Through the above description we have basically completed the main drag sorting functions, but there are two key functions is not resolved. One is calculateMoving method to calculate the target key according to the current offset tranX and tranY.

The calculation rules:

  1. Calculate the current number of rows drag element row according to the length of the list and the number of columns
  2. The calculated x-axis and a width tranX offset current element number i
  3. Y-axis is calculated according to the height of the current element and tranY offset number j
  4. I and j determining the maximum and minimum
  5. The formula endKey = i + columns * j calculates the target key
  6. Analyzing a maximum value of the target key
  7. Return target key
/**
 * 根据当前的手指偏移量计算目标key
 */
calculateMoving(tranX, tranY) {
    let rows = Math.ceil(this.data.list.length / this.data.columns) - 1,
        i = Math.round(tranX / this.item.width),
        j = Math.round(tranY / this.item.height);

    i = i > (this.data.columns - 1) ? (this.data.columns - 1) : i;
    i = i < 0 ? 0 : i;

    j = j < 0 ? 0 : j;
    j = j > rows ? rows : j;

    let endKey = i + this.data.columns * j;

    endKey = endKey >= this.data.list.length ? this.data.list.length - 1 : endKey;

    return endKey
}

insert method

Another main function is to drag insert method does not resolve the ordering. The method according to reorder the array originKey (start key) and EndKey (target key).

Specific ordering rules:

  1. First determines the origin and size of the different logical end processing
  2. Logic processing cycle in list
  3. If the origin is less than the end put between the origin end (contains origin contain no end) of all key elements of minus 1, and set the key value for the end of origin
  4. If the origin is greater than put end to end between the origin (comprising no origin comprise end) all elements plus key 1, key values ​​and the set of end origin
  5. Call getPosition method for rendering
/**
 * 根据起始key和目标key去重新计算每一项的新的key
 */
insert(origin, end) {
    let list;

    if (origin < end) {
        list = this.data.list.map((item) => {
            if (item.key > origin && item.key <= end) {
                item.key = item.key - 1;
            } else if (item.key == origin) {
                item.key = end;
            }
            return item
        });
        this.getPosition(list);

    } else if (origin > end) {
        list = this.data.list.map((item) => {
            if (item.key >= end && item.key < origin) {
                item.key = item.key + 1;
            } else if (item.key == origin) {
                item.key = end;
            }
            return item
        });
        this.getPosition(list);
    }
}

getPosition method

Finally, we insert the above method called getPosition method, which is used to calculate each element tranX and tranY and rendering, the function also needs to call in the initialization rendering time, so the addition of a variable vibrate treated differently judgment.

The execution logic function:

  1. First, the incoming data loop process data, and to calculate the tranX tranY each element according to the formula (this.item.width, this.item.height are the width and height of the element, this.data.columns a column number, item.key sort key value is the current element)
    item.tranX this.item.width * = (item.key this.data.columns%);
    item.tranY = Math.floor (item.key / this.data .columns) * this.item.height;
  2. After setting the data handling list list
  3. And determining whether the triggering event requires performing a dithering logic, which is determined and the initialization call for distinguishing insert method call, do not require an initialization logic behind
  4. ItemTransition first set to true so that when the item added transform animation
  5. Then shake it, wx.vibrateShort (), - ah, that's a good thing
  6. A final copy before setting out to change listData event data sorted thrown to

Finally, note that this function does not change the list really sort, but by pseudo sorted according to key, because if you change the list each item in the order dom structure will change, so we have to reach a silky effect However, the final this.triggerEvent ( 'change', {listData: listData}). when data is actually ordered, and has removed the key, tranX, tranY original data information (where each data has a key, tranX, tranY is because the initialization time to do a deal, so no need to consider the use)

/**
 * 根据排序后 list 数据进行位移计算
 */
getPosition(data, vibrate = true) {
    let list = data.map((item, index) => {
        item.tranX = this.item.width * (item.key % this.data.columns);
        item.tranY = Math.floor(item.key / this.data.columns) * this.item.height;
        return item
    });

    this.setData({
        list: list
    });

    if(!vibrate) return;

    this.setData({
        itemTransition: true
    })

    wx.vibrateShort();

    let listData= [];

    list.forEach((item) => {
        listData[item.key] = item.data
    });

    this.triggerEvent('change', {listData: listData});
}

touchEnd

Writing for so long, three brothers got left the last one, the brother dei seemingly less effort Well, on two lines of code?

Yes, just two lines ... determine whether the drag line, another line clear cached data

touchEnd() {
    if (!this.data.touch) return;

    this.clearData();
}

clearData method

Because of repeated use, so these select logic packed layer.

/**
 * 清除参数
 */
clearData() {
    this.originKey = -1;

    this.setData({
        touch: false,
        cur: -1,
        tranX: 0,
        tranY: 0
    });
}

init method

After the introduction to the three brothers and their cousins, the story got left our init method has.

init method execution logic:

  1. The first is to do treatment plus key, tranX, tranY information such as incoming listData
  2. Then after setting process list and itemTransition to false (such initialization will not see the animation)
  3. Get windowHeight
  4. Higher properties of wide and each item is set aside for later use as this.item
  5. Initialization execution this.getPosition (this.data.list, false)
  6. Set dynamic computed parent element height itemWrapHeight, because here the use of absolute positioning and transform the elements so the parent can not get the height, so manual calculation and assignment
  7. The final step is to obtain the parent element node information item-wrap and calculate whether more than one screen, and set the value overOnePage
init() {
    // 遍历数据源增加扩展项, 以用作排序使用
    let list = this.data.listData.map((item, index) => {
        let data = {
            key: index,
            tranX: 0,
            tranY: 0,
            data: item
        }
        return data
    });

    this.setData({
        list: list,
        itemTransition: false
    });

    this.windowHeight = wx.getSystemInfoSync().windowHeight;

    // 获取每一项的宽高等属性
    this.createSelectorQuery().select(".item").boundingClientRect((res) => {

        let rows = Math.ceil(this.data.list.length / this.data.columns);

        this.item = res;

        this.getPosition(this.data.list, false);

        let itemWrapHeight = rows * res.height;

        this.setData({
            itemWrapHeight: itemWrapHeight
        });

        this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => {
            this.itemWrap = res;

            let overOnePage = itemWrapHeight + res.top > this.windowHeight;

            this.setData({
                overOnePage: overOnePage
            });

        }).exec();
    }).exec();
}

wxml

The following are wxml entire assembly, wherein the specific part of the rendering using the abstract node <item item="{{item.data}}"></item>and each one of the incoming data, the abstract node is used to demonstrate the specific effect of the component itself and decouple code. If you do not bother to performance problems or , style code can be written directly in the assembly.

<view>
    <view style="overflow-x: {{overOnePage ? 'hidden' : 'initial'}}">
        <view class="item-wrap" style="height: {{ itemWrapHeight }}px;">
            <view class="item {{cur == index? 'cur':''}} {{itemTransition ? 'itemTransition':''}}"
                  wx:for="{{list}}"
                  wx:key="{{index}}"
                  id="item{{index}}"
                  data-key="{{item.key}}"
                  data-index="{{index}}"
                  style="transform: translate3d({{index === cur ? tranX : item.tranX}}px, {{index === cur ? tranY: item.tranY}}px, 0px);width: {{100 / columns}}%"
                  bind:longpress="longPress"
                  catch:touchmove="touchMove"
                  catch:touchend="touchEnd">
                <item item="{{item.data}}"></item>
            </view>
        </view>

    </view>
    <view wx:if="{{overOnePage}}" class="indicator">
        <view>滑动此区域滚动页面</view>
    </view>
</view>

wxss

Here I direct scss pull the code out, so see more clearly, specifically the complete text of the code will give an address at the end

@import "../../assets/css/variables";

.item-wrap {
    position: relative;
    .item {
        position: absolute;
        width: 100%;
        z-index: 1;
        &.itemTransition {
            transition: transform 0.3s;
        }
        &.cur {
            z-index: 2;
            background: $mainColorActive;
            transition: initial;
        }
    }
}

.indicator {
    position: fixed;
    z-index: 99999;
    right: 0rpx;
    top: 50%;
    margin-top: -250rpx;
    padding: 20rpx;
    & > view {
        width: 36rpx;
        height: 500rpx;
        background: #ffffff;
        border-radius: 30rpx;
        box-shadow: 0 0 10rpx -4rpx rgba(0, 0, 0, 0.5);
        color: $mainColor;
        padding-top: 90rpx;
        box-sizing: border-box;
        font-size: 24rpx;
        text-align: center;
        opacity: 0.8;
    }
}

At the end of write

The drag and drop components to and fro took me several weeks, counted on the component library is a component in most of the quality, so if you read feel good welcome star. Of course having problems issues mentioned to me on the line, I replied still pretty fast ~ ~

There is this component is limited to micro letter itself api and some features will not slide beyond a screen time. Here, I made a judgment beyond a screen time indicator added a secondary slide, may use slightly for style modify (because it feels a little ugly ...)

Other nothing like the ...

Add that the component is basically how not to use too many small programs related characteristics, according to this thinking should also be realized by h5, if there is a demand h5 aspects should also meet the ...

drag component address

Guess you like

Origin www.cnblogs.com/haha1212/p/11562944.html