xinglie.github.io icon indicating copy to clipboard operation
xinglie.github.io copied to clipboard

移动端组件

Open xinglie opened this issue 5 years ago • 3 comments

dragdrop

/*
    author:[email protected]
 */
let IsW3C = window.getComputedStyle;
let ClearSelection = (t?: () => Selection) => {
    if ((t = window.getSelection)) {
        t().removeAllRanges();
    }
};
let DragPrevent = (e) => {
    e.preventDefault();
};
let DragMoveEvent = ['touchmove'];
let DragEndEvent = ['touchend','touchcancel'];
let DragPreventEvent = ['keydown','fullscreenchange'];

export default {
    ctor() {
        let me = this;
        me.on('destroy', () => {
            me['@:{dd&drag.end}']();
        });
    },
    '@:{dd&drag.end}'(e) {
        let me = this;
        let info = me['@:{dd&move.proxy}'];
        if (info) {
            let fn;
            for (fn of DragMoveEvent) {
                document.removeEventListener(fn, me['@:{dd&move.proxy}']);
            }
            for (fn of DragEndEvent) {
                document.removeEventListener(fn, me['@:{dd&stop.proxy}']);
            }
            for (fn of DragPreventEvent) {
                document.removeEventListener(fn, DragPrevent);
            }
            window.removeEventListener('blur', me['@:{dd&stop.proxy}']);

            delete me['@:{dd&move.proxy}'];
            let stop = me['@:{dd&stop.callback}'];
            if (stop) {
                stop(e);
            }
        }
    },
    '@:{drag.drop}'(e, moveCallback, endCallback) {
        let me = this;
        me['@:{dd&drag.end}']();
        if (e) {
            ClearSelection();
            me['@:{dd&stop.callback}'] = endCallback;
            me['@:{dd&stop.proxy}'] = me['@:{dd&drag.end}'].bind(me);
            me['@:{dd&move.proxy}'] = e => {
                if (moveCallback) {
                    moveCallback(e);
                }
            };
            let fn;
            for (fn of DragMoveEvent) {
                document.addEventListener(fn, me['@:{dd&move.proxy}']);
            }
            for (fn of DragEndEvent) {
                document.addEventListener(fn, me['@:{dd&stop.proxy}']);
            }
            for (fn of DragPreventEvent) {
                document.addEventListener(fn, DragPrevent, {
                    passive: false
                });
            }
            window.addEventListener('blur', me['@:{dd&stop.proxy}']);
        }
    },
    '@:{from.point}'(x, y) {
        let node = null;
        if (document.elementFromPoint) {
            if (!DragPrevent['@:{dd&fixed}'] && IsW3C) {
                DragPrevent['@:{dd&fixed}'] = true;
                DragPrevent['@:{dd&add.scroll}'] = document.elementFromPoint(-1, -1) !== null;
            }
            if (DragPrevent['@:{dd&add.scroll}']) {
                x += window.pageXOffset;
                y += window.pageYOffset;
            }
            node = document.elementFromPoint(x, y);
            while (node && node.nodeType == 3) node = node.parentNode;
        }
        return node;
    },
    '@:{clear.selection}': ClearSelection
};

xinglie avatar May 24 '20 10:05 xinglie

swiper

.swiper{
    margin:0.25rem;
    border-radius: 0.12rem;
    box-shadow: 0 0 4px 1px #e0e0e021;
    overflow: hidden;
    touch-action: pan-x;
}
.swiper-item{
    height: 2.9rem;
    background:#cdcdcd;
    float: left;
    width: 7rem;
    /* border-radius: 0.12rem; */
}
.dots-item{
    width:0.09rem;
    height: 0.07rem;
    background:#fffa;
    margin:0 0.04rem;
    margin-top: -0.2rem;
    transition: all 0.15s;
}
.dots-item-active{
    width: 0.22rem;
    background:#fff;
}
.swiper-root{
    transform: translate3d(-7rem,0,0);
    width: 21rem;
    height: 2.9rem;
    transition: all 0.3s;
}
<div class="swiper">
    <div class="swiper-root" id="root_{{=id}}">
        {{each showCards as sd si}}
        <div class="swiper-item"
            mx-touchstart="@{drag.start}()">
            {{=sd}}
        </div>
        {{/each}}
    </div>
    <div class="flex h-center ptnone pr">
        {{each cards as c i}}
        <div class="dots-item{{if i==current}} dots-item-active{{/if}}"></div>
        {{/each}}
    </div>
</div>
/*
    author:[email protected]
*/
'ref@./index.css';
Magix.applyStyle('@./swiper.css');
import Dragdrop from '../common/dragdrop';
export default Magix.View.extend({
    tmpl: '@swiper.html',
    mixins: [Dragdrop],
    init() {
        this.set({
            cards: [1, 2, 3, 4, 5, 6],
            current: 4
        });
    },
    '@{start.auto.play}'() {
        if (!this['@{auto.play.timer}']) {
            let update = this['@{to.next}'].bind(this);
            this['@{auto.play.timer}'] = setInterval(update, 5e3);
        }
    },
    '@{stop.auto.play}'() {
        if (this['@{auto.play.timer}']) {
            clearInterval(this['@{auto.play.timer}']);
            this['@{auto.play.timer}'] = null;
        }
    },
    '@{update}'() {
        let showCards;
        let { cards, current } = this.get();
        let start = current - 1,
            end = current + 2;
        if (start < 0) start = 0;
        showCards = cards.slice(start, end);
        if (showCards.length < 3) {
            if (current == 0) {
                showCards.unshift(cards[cards.length - 1]);
            }
            if (current == cards.length - 1) {
                showCards.push(cards[0]);
            }
        }
        this.digest({
            showCards
        });
    },
    '@{update.anim}'(x, speed, callback) {
        let root = Magix.node('root_' + this.id);
        let rootStyle = root.style;
        rootStyle.transitionDuration = speed + 's';
        rootStyle.webkitTransitionDuration = speed + 's';
        rootStyle.transform = `translate3d(${x},0,0)`;
        rootStyle.webkitTransform = `translate3d(${x},0,0)`;
        if (callback) {
            if (!speed) {
                callback();
            } else {
                this['@{anim.stop.callback}'] = () => {
                    this['@{anim.stop.callback}'] = null;
                    callback();
                };
                this['@{anim.stop.timer}'] = setTimeout(this['@{anim.stop.callback}'], speed * 1000);
            }
        }
    },
    '@{stop.anim}'() {
        this['@{stop.auto.play}']();
        clearTimeout(this['@{anim.stop.timer}']);
        let fn = this['@{anim.stop.callback}'];
        if (fn) {
            fn();
        }
    },
    '@{to.next}'() {
        let current = this.get('current');
        let cards = this.get('cards');
        current += 1;
        if (current >= cards.length) {
            current = 0;
        }
        this.set({
            current
        });
        this['@{update.anim}']('-14rem', .3, () => {
            this['@{update}']();
            this['@{update.anim}']('-7rem', 0);
            this['@{start.auto.play}']();
        });
    },
    '@{to.prev}'() {
        let current = this.get('current');
        let cards = this.get('cards');
        current -= 1;
        if (current < 0) {
            current = cards.length - 1;
        }
        this.set({
            current
        });
        this['@{update.anim}']('0', .3, () => {
            this['@{update}']();
            this['@{update.anim}']('-7rem', 0);
            this['@{start.auto.play}']();
        });
    },
    '@{drag.start}<touchstart>'(e) {
        if (e.touches.length > 1) return;
        this['@{stop.anim}']();
        let startEvent = e.touches[0];
        let startTime = Date.now();
        let deltaX = 0, deltaY = 0;
        this['@{drag.drop}'](e, moveEvnt => {
            let evt = moveEvnt.touches[0];
            deltaX = evt.clientX - startEvent.clientX;
            deltaY = evt.clientY - startEvent.clientY;
            if (Math.abs(deltaX) > Math.abs(deltaY)) { //水平移动
                moveEvnt.preventDefault();
                this['@{update.anim}'](`calc(-7rem + ${deltaX}px)`, 0);
            }
        }, endEvt => {
            if (Date.now() - startTime < 100 &&
                Math.abs(deltaX) < 5 &&
                Math.abs(deltaY) < 5) {
                alert('opened page');
                return;
            }
            if (endEvt.type.endsWith('cancel') ||
                endEvt.type == 'blur') {
                this['@{update.anim}']('-7rem', .15);
            } else {
                let evt = endEvt.changedTouches[0];
                let endDeltaX = Math.abs(evt.clientX - startEvent.clientX);
                let swipeLeft = evt.clientX < startEvent.clientX; //是否是向左滑动
                let rootWidth = this.root.offsetWidth;
                if (endDeltaX > (rootWidth / 3) ||
                    ((Date.now() - startTime < 550) && endDeltaX > 50)) {
                    if (swipeLeft) {
                        this['@{to.next}']();
                    } else {
                        this['@{to.prev}']();
                    }
                } else {
                    this['@{update.anim}']('-7rem', .15);
                }
            }
        });
    },
    render() {
        if (false) {
            this['@{update}']();
            this['@{start.auto.play}']();
        } else {
            this.digest();
        }
    }
});

xinglie avatar May 24 '20 10:05 xinglie

yscroller

html,
body{
    touch-action: pan-y;
    user-select: none;
}
.page{
    height:200px;
    overflow: hidden;
    background-color: tomato;
    touch-action: none;
}
.scroller{
    transform: translate3d(0,0,0);
}
<div class="page" id="p_{{=$viewId}}">
    <div id="s_{{=$viewId}}" class="scroller" mx-touchstart="@:{start.drag}()">
        <div style="background: #ccc;">
            {{for(let i=0;i<50;i++)}}
            <div>div{{=i}}</div>
            {{/for}}
        </div>
    </div>
</div>
import Magix from 'magix5';
import Dragdrop from './dragdrop';
Magix.applyStyle('@:./xx.css');
let updateStyle = (node, key, value) => {
    let style = node.style;
    style[key] = value;
};
export default Magix.View.extend({
    tmpl: '@:./xx.html',
    mixins: [Dragdrop],
    '@:{init}'() {
        let scroller = Magix.node(`s_${this.id}`);
        let page = Magix.node(`p_${this.id}`);
        this['@:{offset.top}'] = 0;
        this['@:{scroller}'] = scroller;
        this['@:{max.height}'] = -scroller.scrollHeight + page.clientHeight;
    },
    async render() {
        await this.digest();
        this['@:{init}']();
    },
    '@:{start.drag}<touchstart>'(e) {
        let startTime = Date.now();
        let startEvent = e.touches[0];
        let startPageY = startEvent.pageY;
        let scroller = this['@:{scroller}'];
        let maxHeight = this['@:{max.height}'];
        updateStyle(scroller, 'transition', 'none');
        this['@:{drag.drop}'](e, evt => {
            let now = Date.now();
            let moveEvent = evt.touches[0];
            let diffPageY = moveEvent.pageY - startPageY;
            let movePageY = diffPageY + this['@:{offset.top}'];
            if (now - startTime > 300 && Math.abs(diffPageY) < 10) {
                return;
            }
            if (movePageY > 0) {
                movePageY /= 3;
            } else if (movePageY < maxHeight) {
                movePageY = movePageY - maxHeight;
                movePageY = movePageY / 3 + maxHeight;
            }
            updateStyle(scroller, 'transform', 'translate3d(0, ' + movePageY + 'px,0)');
        }, evt => {
            if (evt) {
                let now = Date.now();
                let endEvent = evt.changedTouches[0];
                let offsetHeight = endEvent.pageY - startPageY;
                let st = this['@:{offset.top}'];
                let duration = now - startTime;
                st += offsetHeight;
                if (st > 0 || st < maxHeight) {
                    updateStyle(scroller, 'transition', 'all .5s');
                } else if (duration < 300) {
                    let speed = Math.abs(offsetHeight) / duration;
                    let time = duration * speed * 12;
                    //alert([duration, speed, time]);
                    let start = st,
                        rate = 1;
                    if (time > 1500) time = 1500;
                    st += offsetHeight * speed * 8;
                    let ease = '.1,.7,.1,1';
                    if (st > 0 || st < maxHeight) {
                        ease = '.1,.7,.4,1.2';
                        if (st > 0) {
                            rate = (0 - start) / (st - start);
                        } else {
                            rate = (maxHeight - st) / (start - st);
                        }
                        time *= rate;
                        if (time < 500) time = 500;
                    }
                    updateStyle(scroller, 'transition', 'all ' + time + 'ms ' + 'cubic-bezier(' + ease + ') 0s');
                }
                if (st > 0) {
                    st = 0;
                } else if (st < maxHeight) {
                    st = maxHeight;
                }
                this['@:{offset.top}'] = st;
                updateStyle(scroller, 'transform', 'translate3d(0, ' + st + 'px,0)');
            }
        });
    }
})

xinglie avatar May 24 '20 12:05 xinglie

模拟input

移动端唤起软键盘会导致页面fixed定位等布局失效,对于需要输入数字等场景的,我们可以模块输入、模拟键盘,该组件仅模拟输入框。 输入框支持光标,支持光标移动,支持长按光标变大拖动改变位置 长按光标变大时,光标不再闪烁。输入、删除内容时光标不再闪烁

@keyframes caret-blink{
  0% {
    opacity: 1;
  }
  50%{
    opacity: 1;
  }
  80%{
    opacity: 0;
  }
  100%{
    opacity: 0;
  }
}
.caret-anim{
  animation: caret-blink .8s infinite;
}
.caret{
  width: 2px;
  height: 60%;
  top: 20%;
  left:0;
  background: #f00;
  position: absolute;
  transform: translateX(3px);
  display: none;
}
.caret-zoomin{
  height: 80%;
  top:10%;
  width: 2px;
}
.input{
  border: solid 1px #eee;
  position: relative;
  height: 30px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  min-width: 200px;
  touch-action: none;
}

<div class="input" id="r_{{=$viewId}}" mx-touchstart="@:{move.caret}()" mx-contextmenu="@:{prevent}()">
    {{each values as v}}
    <span>{{=v}}</span>
    {{/each}}
    <div class="caret caret-anim" id="c_{{=$viewId}}"></div>
</div>
import Magix from 'magix5';
import Dragdrop from './dragdrop';
Magix.applyStyle('@:./t.css');
let updateStyle = (node, key, value) => {
    node.style[key] = value;
};
export default Magix.View.extend({
    tmpl: '@:./t.html',
    mixins: [Dragdrop],
    init() {
        this.set({
            focused: false
        });
    },
    '@:{update.caret}'(pageX) {
        let pos = this.get('pos');
        let node = Magix.node('c_' + this.id);
        let root = Magix.node('r_' + this.id);
        let spans = this.root.querySelectorAll('span');
        let pBound = root.getBoundingClientRect();
        if (pageX !== undefined) {
            let first = spans[0];
            let bound = first.getBoundingClientRect();
            if (pageX <= bound.left + bound.width / 2) {
                updateStyle(node, 'transform', 'translateX(3px)');
                this.set({
                    pos: 0
                });
            } else {
                for (let i = spans.length; i--;) {
                    let span = spans[i];
                    let bound = span.getBoundingClientRect();
                    if ((bound.left + bound.width / 2) <= pageX) {
                        updateStyle(node, 'transform', 'translateX(' + (bound.left + bound.width - pBound.left - 1) + 'px)');
                        this.set({
                            pos: i + 1
                        });
                        break;
                    }
                }
            }
        } else {
            if (pos == 0) {
                updateStyle(node, 'transform', 'translateX(3px)');
            } else {
                let span = spans[pos - 1];
                let bound = span.getBoundingClientRect();
                updateStyle(node, 'transform', 'translateX(' + (bound.left + bound.width - pBound.left - 1) + 'px)');
            }
        }
    },
    '@:{move.caret}'(to) {
        let values = this.get('values');
        if (to < 0) to = 0;
        else if (to > values.length) to = values.length;
        this.set({
            pos: to
        });
        this['@:{update.caret}']();
    },
    '@:{update.focus}'(e) {
        let caret = Magix.node('c_' + this.id);
        if (e) {
            caret.style.display = 'block';
            this.set({
                focused: true
            });
            this['@:{update.caret}'](e.pageX);
        } else {
            caret.style.display = 'none';
            this.set({
                focused: false
            });
        }
    },
    async '@:{add}'(v) {
        let values = this.get('values');
        let pos = this.get('pos');
        let vs = v.split('');
        values.splice(pos, 0, ...vs);
        pos += vs.length;
        await this.digest({
            values,
            pos
        });
        this['@:{update.caret}']();
    },
    async '@:{remove}'() {
        let values = this.get('values');
        let pos = this.get('pos');
        if (pos > 0) {
            pos--;
            values.splice(pos, 1);
            await this.digest({
                values,
                pos
            });
            this['@:{update.caret}']();
        }
    },
    '@:{move.caret}<touchstart>'(e) {
        let se = e.changedTouches[0];
        let dragMove = 0;
        let root = Magix.node('r_' + this.id);
        let caret = Magix.node('c_' + this.id);
        let rBound, moved = 0;
        let mark = Magix.mark(this, '@:{update.caret}');
        let timer = setTimeout(() => {
            if (mark()) {
                dragMove = 1;
                rBound = root.getBoundingClientRect();
                updateStyle(caret, 'transition', 'all .2s');
                updateStyle(caret, 'transform', `translateX(${se.pageX - rBound.left - 1}px)`);
                caret.classList.remove('@:./t.css:caret-anim');
                caret.classList.add('@:./t.css:caret-zoomin');
            }
        }, 800);
        this['@:{drag.drop}'](e, evt => {
            let me = evt.touches[0];
            if (Math.abs(me.pageX - se.pageX) > 5 ||
                Math.abs(me.pageY - se.pageY) > 5) {
                clearTimeout(timer);
                moved = 1;
            }
            if (dragMove) {
                updateStyle(caret, 'transition', 'none');
                let newLeft = me.pageX - rBound.left;
                if (newLeft < 0) newLeft = 0;
                else if (newLeft > rBound.width - 4) newLeft = rBound.width - 4;
                updateStyle(caret, 'transform', `translateX(${newLeft}px)`);
                se = me;
            }
        }, evt => {
            if (dragMove) {
                mark = Magix.mark(this, '@:{update.caret}');
                updateStyle(caret, 'transition', 'all .2s');
                setTimeout(() => {
                    if (mark()) {
                        updateStyle(caret, 'transition', 'none');
                    }
                }, 200);
            }
            caret.classList.add('@:./t.css:caret-anim');
            caret.classList.remove('@:./t.css:caret-zoomin');
            if (!moved || dragMove) {
                this['@:{update.caret}'](se.pageX);
            }
            clearTimeout(timer);
        });
    },
    '@:{prevent}<contextmenu>'(e) {
        e.preventDefault();
    },
    '$doc<touchstart>'(e) {
        let root = Magix.node('r_' + this.id);
        if (Magix.inside(e.target, root)) {
            if (!this.get('focused')) {
                this['@:{update.focus}'](e.changedTouches[0]);
            }
        } else if (!Magix.inside(e.target, Magix.node('kb'))) {
            this['@:{update.focus}']();
        }
    },
    '@:{keep.caret.when.change}'(callback) {
        let caret = Magix.node('c_' + this.id);
        let mark = Magix.mark(this, '@:{caret.add.anim}');
        clearTimeout(this['@:{add.anim.timer}']);
        caret.classList.remove('@:./t.css:caret-anim');
        callback();
        this['@:{add.anim.timer}'] = setTimeout(() => {
            if (mark()) {
                caret.classList.add('@:./t.css:caret-anim');
            }
        }, 200);
    },
    '$doc<keypress>'(e) {
        if (this.get('focused')) {
            this['@:{keep.caret.when.change}'](() => {
                this['@:{add}'](e.key);
            });
        }
    },
    '$doc<keydown>'(e) {
        if (this.get('focused')) {
            if (e.keyCode == 8) {
                this['@:{keep.caret.when.change}'](this['@:{remove}'].bind(this));
            }
        }
    },
    async render() {
        await this.digest({
            pos: 3,
            values: [1, 2, 3, 'a', 'b', 'c']
        });
        this['@:{update.caret}']();
    }
});

xinglie avatar May 27 '20 12:05 xinglie