xinglie.github.io
xinglie.github.io copied to clipboard
移动端组件
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
};
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();
}
}
});
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)');
}
});
}
})
模拟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}']();
}
});