Commit 5699827a authored by liujiaxin's avatar liujiaxin

1

parent 8a51c964
......@@ -30,7 +30,10 @@
],
"styles": [
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
"src/styles.css"
"./node_modules/font-awesome/css/font-awesome.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"./node_modules/animate.css/animate.min.css",
"src/styles.scss"
],
"scripts": []
},
......
@import '../style/common_mixin.css';
.model-content {
width: 100%;
height: 100%;
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 50%;
font-size: 2rem;
color: #999;
transition: all .3s;
}
.dynamic-delete-button:hover {
color: #777;
}
<form nz-form style="width:100%">
<nz-form-item
style="width: 49.8%;float: left;padding: 0 .5rem"
*ngFor="let control of picArray;let i = index">
<nz-form-control [nzXs]="24" [nzSm]="24" [nzOffset]="0">
<div nz-col class="gutter-row" [nzSpan]="24">
<app-upload-image-with-preview
style="width: 300px"
[picUrl]="item.contentObj.pics && item.contentObj.pics[i] ? item.contentObj.pics[i] : null"
(imageUploaded)="onImageUploadSuccess(control, $event)"
></app-upload-image-with-preview>
</div>
<div *ngIf="picArray.length>1" style="position: absolute;right: .5rem;top: .5rem;">
<i class="anticon anticon-close dynamic-delete-button"
(click)="removeField(control,$event)"></i>
<div class="model-content">
<div style="position: absolute; left: 200px; top: 100px; width: 800px;">
<input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
<app-upload-image-with-preview
[picUrl]="item.pic_url"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')"
></app-upload-image-with-preview>
<app-audio-recorder
[audioUrl]="item.audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
></app-audio-recorder>
<app-custom-hot-zone></app-custom-hot-zone>
<app-upload-video></app-upload-video>
<app-lesson-title-config></app-lesson-title-config>
</div>
</div>
\ No newline at end of file
</div>
</nz-form-control>
</nz-form-item>
<nz-form-item style="margin-top: 1rem;clear: both">
<nz-form-control [nzXs]="{span:24,offset:0}" [nzSm]="{span:20,offset:4}">
<button nz-button nzType="dashed" style="width:60%" (click)="addPictureField(null, $event)"><i class="anticon anticon-plus"></i> Add Picture</button>
</nz-form-control>
</nz-form-item>
</form>
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd';
......@@ -10,26 +11,47 @@ import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, Ap
export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用
saveKey = "test_0011";
saveKey = 'ww_sequence';
// 储存对象
item;
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) {
picArray: Array<{
controlInstance: string,
progress: number,
uploadStatus: number,
uploading: boolean,
res_id: number
}>;
constructor(private appRef: ApplicationRef,
private nzMessageService: NzMessageService,
private changeDetectorRef: ChangeDetectorRef) {
}
ngOnInit() {
this.item = {};
this.item = {
contentObj: {}
};
// 获取存储的数据
(<any> window).courseware.getData((data) => {
(window as any).courseware.getData((data) => {
if (data) {
this.item = data;
}
this.picArray = [];
if (Array.isArray(this.item.contentObj.pics) && this.item.contentObj.pics.length > 0) {
for (const p of this.item.contentObj.pics) {
this.addPictureField({
controlInstance: `${p}`,
res_id: p
});
}
} else {
this.addPictureField();
}
this.init();
this.changeDetectorRef.markForCheck();
......@@ -53,26 +75,26 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}
/**
* 储存图片数据
* @param e
*/
onImageUploadSuccess(e, key) {
this.item[key] = e.url;
this.save();
}
/**
* 储存音频数据
* @param e
*/
onAudioUploadSuccess(e, key) {
this.item[key] = e.url;
this.save();
}
//
// /**
// * 储存图片数据
// * @param e
// */
// onImageUploadSuccess(e, key) {
//
// this.item[key] = e.url;
// this.save();
// }
// /**
// * 储存音频数据
// * @param e
// */
// onAudioUploadSuccess(e, key) {
//
// this.item[key] = e.url;
// this.save();
// }
......@@ -80,7 +102,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存数据
*/
save() {
(<any> window).courseware.setData(this.item, null, this.saveKey);
(window as any).courseware.setData(this.item, null, this.saveKey);
this.refresh();
}
......@@ -93,5 +115,56 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}, 1);
}
addPictureField(control?: any, e?: MouseEvent): void {
// debugger;
if (e) {
e.preventDefault();
}
// const sts = this.checkForm();
// if (!sts) {
// return;
// }
const id = this.picArray.length ; // (this.controlArray.length > 0) ? this.controlArray[ this.controlArray.length - 1 ] : 0;
if (id >= 9) {
this.nzMessageService.info('you can only add nine item!');
return;
}
if (!control) {
control = {
controlInstance: `${id}`,
res_id: 0
};
}
const index = this.picArray.push(control);
}
removeField(i: any, e: MouseEvent): void {
e.preventDefault();
if (this.picArray.length > 1) {
const index = this.picArray.indexOf(i);
this.picArray.splice(index, 1);
// console.log(this.controlArray);
this.savedata();
}
}
onImageUploadSuccess(control, info) {
// console.log(control, info)
control.res_id = info.url;
this.savedata();
}
savedata() {
this.item.contentObj.pics = [];
for (const idx in this.picArray) {
this.item.contentObj.pics[idx] = this.picArray[idx].res_id;
}
this.save();
}
}
.game-container {
width: 100%;
height: 100%;
background: #ffffff;
background-size: cover;
}
#canvas {
}
@font-face
{
font-family: 'BRLNSDB';
src: url("../../assets/font/BRLNSDB.TTF") ;
}
<div class="game-container" #wrap>
<canvas id="canvas" #canvas></canvas>
<div class="player-preview-sequence">
<div class="content-wrap ">
<!-- aspectRatioSizer-->
<!-- <svg viewBox="0 0 4 3" style="max-width: 100vmin;max-height: 100vmin;"/>-->
<!--[style.padding]="randPics.length > 2 && randPics.length <= 4 ? '12% 20% 0' : '15% 5% 0'"-->
<div class="item-wrap" #boxContainer>
<div *ngFor="let pic of randPics index as i"
class="gutter-row item"
[attr.data-idx]="pic.idx"
>
<div class="item-box">
<img src="/assets/sequence2/seq_item_bg.png" style="width: 100%;height: 100%" alt="">
<div class="item-pic drop-target"
[attr.data-idx]="pic.idx"
[id]="'tgt'+i"
(click)="checkSelectedIndex($event, pic.idx)"
[style.background-image]="pic.res_id | backgroundImage ">
</div>
<div class="item-label {{'p-font-size-'+randPics.length}}" *ngIf="pic.isRight">{{pic.idx + 1}}</div>
</div>
</div>
</div>
<div class="seq-wrap" >
<img class="seq-wrap-bg" src="/assets/sequence2/rectangle.png" style="width: 100%;height: 100%" alt="">
<div class="seq-num-box" #indexNumBox>
<img
*ngFor="let i of _itemsArr"
src="/assets/sequence2/{{i}}.png" alt="">
</div>
<div class="answer-wrap" >
<img class="btn-answer"
(click)="showRightAnswer()"
src="/assets/sequence2/answer.png" alt="">
</div>
</div>
</div>
<!--<div class="bubble-box" #bubbleContainer>-->
<!--<div *ngFor="let pic of randPics index as i"-->
<!--[id]="'bb'+i"-->
<!--[class]="'bubble b'+i+(randPics.length<5 ? ' less4item' : '')" >-->
<!--<div class="bdhandle">-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
</div>
.player-preview-sequence{
width: 100%;
height: 100%;
background-image: url('../../assets/sequence2/bg_seq.png');
background-position: 50% 50%;
background-size: cover;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
.content-wrap{
padding-top: 3%;
padding-left: 3%;
padding-right: 3%;
position: relative;
width: 100%;
height: 88%;
display: flex;
flex-flow: row wrap;
.item-wrap{
position: relative;
display: flex;
flex-flow: row wrap;
//padding-right: 18vh;
//top: 54%;
//transform: translateY(-50%);
//width: 100%;
margin-right: 18vh;
display: inline-grid;
grid-template-columns: repeat(auto-fit, var(--item-width));
grid-template-rows: repeat(auto-fit, var(--item-height));
place-content: space-evenly;
width: 100%;
height: 100%;
//background-color: green;
}
.seq-wrap{
width: 12vh;
height: calc( 100% - 6vh);
position: absolute;
right: 5%;
img.seq-wrap-bg{
width: 100%;
height: 100%;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.seq-num-box{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding-top: 2%;
padding-bottom: 2%;
img{
margin: 4% auto 2%;
display: block;
height: 10%;
}
img.right{
animation: fadeOut .5s;
}
@keyframes fadeOut {
0% {opacity: 1}
100% {opacity: 0;visibility: hidden;height: 0; }
}
img.wrong{
animation: fadeOutIn 1s;
}
@keyframes fadeOutIn {
0% {opacity: 1}
50% {opacity: 0;}
100% {opacity: 1;}
}
}
}
.answer-wrap{
width: 13vh;
padding-top: 61.65%;
height: 0;
//right: 4.5%;
//top: -10vh;
bottom: 0;
position: absolute;
.btn-answer{
cursor: pointer;
}
img{
max-width: 100%;
max-height: 100%;
position: absolute;
left: 0;
}
}
}
.content-wrap:after {
content: "";
flex: auto;
}
.gutter-row{
float: none;
padding-left: .5rem;
padding-right: .5rem;
.item-box{
/*background-image: url('../../../../assets/sequence/item-bg.png');
background-size: 100%;
background-repeat: no-repeat;
width: 100%;
height: 74.75%;*/
/*background-image: url('../../../../assets/sequence/item-bg.png');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
position: relative;
height: 100%;
overflow: hidden;*/
position: relative;
top: 50%;
transform: translateY(-50%);
.content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.content:before {
padding-top: 56.25%;
/* 16:9 ratio */
display: block;
content: '';
}
.item-pic{
width: 95%;
height: 70%;
background-size: contain;
background-position: 50% 50%;
background-repeat: no-repeat;
position: absolute;
top: 10%;
left: 2.5%;
cursor: pointer;
}
.item-pic.selected-right{
cursor: default;
}
.item-label{
position: absolute;
right: 6%;
bottom: 0%;
width: 17%;
height: 21%;
color: #fff;
font-size: calc(0.030 * 100vh);
font-weight: 900;
text-align: center;
}
}
}
}
.p-font-size-1, .p-font-size-2 {
font-size: calc(0.04 * 100vh) !important;
}
.p-font-size-3, .p-font-size-4 {
font-size: calc(0.05 * 100vh) !important;
line-height: 130%!important;
}
.aspectRatioSizer {
display: grid;
}
.aspectRatioSizer > * {
grid-area: 1 / 1 / 2 / 2;
}
import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core';
import { Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener, AfterViewInit } from '@angular/core';
import {
Label,
......@@ -18,662 +18,239 @@ import TWEEN from '@tweenjs/tween.js';
@Component({
selector: 'app-play',
templateUrl: './play.component.html',
styleUrls: ['./play.component.css']
styleUrls: ['./play.component.scss']
})
export class PlayComponent implements OnInit, OnDestroy {
@ViewChild('canvas', {static: true }) canvas: ElementRef;
@ViewChild('wrap', {static: true }) wrap: ElementRef;
// 数据
export class PlayComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('indexNumBox', {static: true})
indexNumBox: ElementRef;
@ViewChild('boxContainer', {static: true})
boxContainer: ElementRef;
currentSeqIndex = 0;
bbFlying = false;
col: number;
randPics: Array<any>;
rightAudio = new Audio();
wrongAudio = new Audio();
animIng = false;
_itemsArr = [];
data;
ctx;
canvasWidth = 1280; // canvas实际宽度
canvasHeight = 720; // canvas实际高度
canvasBaseW = 1280; // canvas 资源预设宽度
canvasBaseH = 720; // canvas 资源预设高度
mx; // 点击x坐标
my; // 点击y坐标
// 资源
rawImages = new Map(res);
rawAudios = new Map(resAudio);
images = new Map();
animationId: any;
winResizeEventStream = new Subject();
audioObj = {};
renderArr;
mapScale = 1;
canvasLeft;
canvasTop;
saveKey = 'test_0011';
btnLeft;
btnRight;
pic1;
pic2;
canTouch = true;
curPic;
saveKey = 'ww_sequence';
@HostListener('window:resize', ['$event'])
onResize(event) {
this.winResizeEventStream.next();
}
ngOnInit() {
this.data = {};
// 获取数据
const getData = (<any> window).courseware.getData;
getData((data) => {
if (data && typeof data == 'object') {
this.data = data;
}
// console.log('data:' , data);
// 初始化 各事件监听
this.initListener();
// 若无数据 则为预览模式 需要填充一些默认数据用来显示
this.initDefaultData();
// 初始化 音频资源
this.initAudio();
// 初始化 图片资源
this.initImg();
// 开始预加载资源
this.load();
}, this.saveKey);
this.resetContainerItems();
}
ngOnDestroy() {
window['curCtx'] = null;
window.cancelAnimationFrame(this.animationId);
constructor() {
this.rightAudio.src = '/assets/right.mp3';
this.rightAudio.load();
this.wrongAudio.src = '/assets/wrong.mp3';
this.wrongAudio.load();
}
load() {
// 预加载资源
this.loadResources().then(() => {
window["air"].hideAirClassLoading(this.saveKey, this.data);
this.init();
this.update();
});
ngAfterViewInit(): void {
this.resetContainerItems();
}
ngOnDestroy(){
init() {
this.initCtx();
this.initData();
this.initView();
}
initCtx() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.canvas.nativeElement.width = this.wrap.nativeElement.clientWidth;
this.canvas.nativeElement.height = this.wrap.nativeElement.clientHeight;
this.ctx = this.canvas.nativeElement.getContext('2d');
this.canvas.nativeElement.width = this.canvasWidth;
this.canvas.nativeElement.height = this.canvasHeight;
window['curCtx'] = this.ctx;
}
updateItem(item) {
if (item) {
item.update();
}
}
updateArr(arr) {
if (!arr) {
return;
}
for (let i = 0; i < arr.length; i++) {
arr[i].update(this);
shuffle(arr) {
const a = [].concat(arr);
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
ngOnInit() {
this.data = {};
// 获取数据
const getData = (window as any).courseware.getData;
getData((data) => {
if (data && typeof data === 'object') {
this.data = data;
this.currentSeqIndex = 0;
if (!Array.isArray(this.data.contentObj.pics)) {
this.data.contentObj.pics = [];
}
this.randPics = this.shuffle(this.data.contentObj.pics.map((res_id, idx) => {
return {res_id, idx };
}));
const pl = this.randPics.length;
this._itemsArr = Array(pl).fill(null).map((_, i) => i + 1);
if ( pl <= 4) {
this.col = 12;
} else if (pl > 4 ) {
this.col = 8;
}
initListener() {
this.winResizeEventStream
.pipe(debounceTime(500))
.subscribe(data => {
this.renderAfterResize();
});
// ---------------------------------------------
const setParentOffset = () => {
const rect = this.canvas.nativeElement.getBoundingClientRect();
this.canvasLeft = rect.left;
this.canvasTop = rect.top;
};
const setMxMyByTouch = (event) => {
if (event.touches.length <= 0) {
return;
}
if (this.canvasLeft == null) {
setParentOffset();
}
this.mx = event.touches[0].pageX - this.canvasLeft;
this.my = event.touches[0].pageY - this.canvasTop;
};
const setMxMyByMouse = (event) => {
this.mx = event.offsetX;
this.my = event.offsetY;
};
// ---------------------------------------------
let firstTouch = true;
const touchDownFunc = (e) => {
if (firstTouch) {
firstTouch = false;
removeMouseListener();
}
setMxMyByTouch(e);
this.mapDown(e);
};
const touchMoveFunc = (e) => {
setMxMyByTouch(e);
this.mapMove(e);
};
const touchUpFunc = (e) => {
setMxMyByTouch(e);
this.mapUp(e);
};
const mouseDownFunc = (e) => {
if (firstTouch) {
firstTouch = false;
removeTouchListener();
}
setMxMyByMouse(e);
this.mapDown(e);
};
const mouseMoveFunc = (e) => {
setMxMyByMouse(e);
this.mapMove(e);
};
const mouseUpFunc = (e) => {
setMxMyByMouse(e);
this.mapUp(e);
};
const element = this.canvas.nativeElement;
const addTouchListener = () => {
element.addEventListener('touchstart', touchDownFunc);
element.addEventListener('touchmove', touchMoveFunc);
element.addEventListener('touchend', touchUpFunc);
element.addEventListener('touchcancel', touchUpFunc);
};
const removeTouchListener = () => {
element.removeEventListener('touchstart', touchDownFunc);
element.removeEventListener('touchmove', touchMoveFunc);
element.removeEventListener('touchend', touchUpFunc);
element.removeEventListener('touchcancel', touchUpFunc);
};
const addMouseListener = () => {
element.addEventListener('mousedown', mouseDownFunc);
element.addEventListener('mousemove', mouseMoveFunc);
element.addEventListener('mouseup', mouseUpFunc);
};
const removeMouseListener = () => {
element.removeEventListener('mousedown', mouseDownFunc);
element.removeEventListener('mousemove', mouseMoveFunc);
element.removeEventListener('mouseup', mouseUpFunc);
};
addMouseListener();
addTouchListener();
}
playAudio(key, now = false, callback = null) {
const audio = this.audioObj[key];
if (audio) {
if (now) {
audio.pause();
audio.currentTime = 0;
}
if (callback) {
audio.onended = () => {
callback();
};
if (window['air']) {
window['air'].hideAirClassLoading(this.saveKey, this.data);
}
audio.play();
}
}
loadResources() {
const pr = [];
this.rawImages.forEach((value, key) => {// 预加载图片
const p = this.preload(value)
.then(img => {
this.images.set(key, img);
})
.catch(err => console.log(err));
pr.push(p);
});
this.rawAudios.forEach((value, key) => {// 预加载音频
const a = this.preloadAudio(value)
.then(() => {
// this.images.set(key, img);
})
.catch(err => console.log(err));
pr.push(a);
});
return Promise.all(pr);
}
preload(url) {
return new Promise((resolve, reject) => {
const img = new Image();
// img.crossOrigin = "anonymous";
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
preloadAudio(url) {
return new Promise((resolve, reject) => {
const audio = new Audio();
audio.oncanplay = (a) => {
resolve();
};
audio.onerror = () => {
reject();
};
audio.src = url;
audio.load();
});
}
renderAfterResize() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.init();
}
checkClickTarget(target) {
const rect = target.getBoundingBox();
if (this.checkPointInRect(this.mx, this.my, rect)) {
return true;
}
return false;
}
getWorlRect(target) {
let rect = target.getBoundingBox();
if (target.parent) {
}, this.saveKey);
const pRect = this.getWorlRect(target.parent);
rect.x += pRect.x;
rect.y += pRect.y;
}
return rect;
}
checkPointInRect(x, y, rect) {
if (x >= rect.x && x <= rect.x + rect.width) {
if (y >= rect.y && y <= rect.y + rect.height) {
return true;
move(el, to_x, to_y, cb?: () => void) {
// el.style.position = 'absolute';
const x = parseInt(el.offsetLeft, 10);
const y = parseInt(el.offsetTop, 10);
const delta_x = (to_x - x) / 60;
const delta_y = (to_y - y) / 60;
let anim_timer = null;
let c = 0;
anim_timer = setInterval(() => {
if (c >= 60) {
clearInterval(anim_timer);
anim_timer = null;
c = 0;
if (cb) {
cb();
}
return;
}
}
return false;
c++;
el.style.left = x + c * delta_x + 'px';
el.style.top = y + c * delta_y + 'px';
// console.log(c, el.style.left, el.style.top);
}, 16);
}
addUrlToAudioObj(key, url = null, vlomue = 1, loop = false, callback = null) {
const audioObj = this.audioObj;
if (url == null) {
url = key;
checkSelectedIndex(evt, idx) {
if (this.animIng) {
return;
}
this.rawAudios.set(key, url);
const audio = new Audio();
audio.src = url;
audio.load();
audio.loop = loop;
audio.volume = vlomue;
audioObj[key] = audio;
if (callback) {
audio.onended = () => {
callback();
};
this.animIng = true;
const imgs = this.indexNumBox.nativeElement.querySelectorAll('img');
const img = imgs[this.currentSeqIndex];
if (!img) {
return;
}
}
addUrlToImages(url) {
this.rawImages.set(url, url);
}
// ======================================================编写区域==========================================================================
/**
* 添加默认数据 便于无数据时的展示
*/
initDefaultData() {
if (!this.data.pic_url) {
this.data.pic_url = 'assets/play/default/pic.jpg';
this.data.pic_url_2 = 'assets/play/default/pic.jpg';
img.classList.remove('right');
img.classList.remove('wrong');
// img.offsetWidth;
const newone = img.cloneNode(true);
img.parentNode.replaceChild(newone, img);
if ( this.currentSeqIndex === idx ) {
this.rightAudio.pause();
this.rightAudio.currentTime = 0;
this.rightAudio.play();
newone.classList.add('right');
newone.addEventListener('animationend', () => {
// console.log('aaaa');
newone.style.display = 'none';
this.currentSeqIndex++ ;
this.animIng = false;
this.randPics.find(item => item.idx === idx).isRight = true;
});
} else {
this.wrongAudio.pause();
this.wrongAudio.currentTime = 0;
this.wrongAudio.play();
newone.classList.add('wrong');
this.animIng = false;
}
}
showRightAnswer() {
const boxs = this.boxContainer.nativeElement.children; // .querySelectorAll('.gutter-row');
/**
* 添加预加载图片
*/
initImg() {
this.addUrlToImages(this.data.pic_url);
this.addUrlToImages(this.data.pic_url_2);
}
/**
* 添加预加载音频
*/
initAudio() {
// 音频资源
this.addUrlToAudioObj(this.data.audio_url);
this.addUrlToAudioObj(this.data.audio_url_2);
// 音效
this.addUrlToAudioObj('click', this.rawAudios.get('click'), 0.3);
}
/**
* 初始化数据
*/
initData() {
const sx = this.canvasWidth / this.canvasBaseW;
const sy = this.canvasHeight / this.canvasBaseH;
const s = Math.min(sx, sy);
this.mapScale = s;
// this.mapScale = sx;
// this.mapScale = sy;
this.renderArr = [];
}
/**
* 初始化试图
*/
initView() {
this.initPic();
this.initBottomPart();
}
initBottomPart() {
const btnLeft = new MySprite();
btnLeft.init(this.images.get('btn_left'));
btnLeft.x = this.canvasWidth - 150 * this.mapScale;
btnLeft.y = this.canvasHeight - 100 * this.mapScale;
btnLeft.setScaleXY(this.mapScale);
this.renderArr.push(btnLeft);
this.btnLeft = btnLeft;
const btnRight = new MySprite();
btnRight.init(this.images.get('btn_right'));
btnRight.x = this.canvasWidth - 50 * this.mapScale;
btnRight.y = this.canvasHeight - 100 * this.mapScale;
btnRight.setScaleXY(this.mapScale);
this.renderArr.push(btnRight);
this.btnRight = btnRight;
}
initPic() {
const maxW = this.canvasWidth * 0.7;
const pic1 = new MySprite();
pic1.init(this.images.get(this.data.pic_url));
pic1.x = this.canvasWidth / 2;
pic1.y = this.canvasHeight / 2;
pic1.setScaleXY(maxW / pic1.width);
this.renderArr.push(pic1);
this.pic1 = pic1;
const label1 = new Label();
label1.text = this.data.text;
label1.textAlign = 'center';
label1.fontSize = 50;
label1.fontName = 'BRLNSDB';
label1.fontColor = '#ffffff';
pic1.addChild(label1);
const pic2 = new MySprite();
pic2.init(this.images.get(this.data.pic_url_2));
pic2.x = this.canvasWidth / 2 + this.canvasWidth;
pic2.y = this.canvasHeight / 2;
pic2.setScaleXY(maxW / pic2.width);
this.renderArr.push(pic2);
this.pic2 = pic2;
this.curPic = pic1;
}
btnLeftClicked() {
this.lastPage();
}
btnRightClicked() {
this.nextPage();
}
lastPage() {
if (this.curPic == this.pic1) {
return;
const els = [];
for (const box of boxs) {
els.push(box);
// els.push(box.cloneNode(true));
// box.remove();
}
this.canTouch = false;
const moveLen = this.canvasWidth;
tweenChange(this.pic1, {x: this.pic1.x + moveLen}, 1);
tweenChange(this.pic2, {x: this.pic2.x + moveLen}, 1, () => {
this.canTouch = true;
this.curPic = this.pic1;
els.sort((a, b) => {
const one = parseInt(a.dataset.idx, 10);
const two = parseInt(b.dataset.idx, 10);
return one - two;
});
}
nextPage() {
if (this.curPic == this.pic2) {
return;
for (const el of els) {
this.boxContainer.nativeElement.appendChild(el);
}
this.canTouch = false;
const moveLen = this.canvasWidth;
tweenChange(this.pic1, {x: this.pic1.x - moveLen}, 1);
tweenChange(this.pic2, {x: this.pic2.x - moveLen}, 1, () => {
this.canTouch = true;
this.curPic = this.pic2;
});
}
pic1Clicked() {
this.playAudio(this.data.audio_url);
}
pic2Clicked() {
this.playAudio(this.data.audio_url_2);
}
mapDown(event) {
if (!this.canTouch) {
return;
}
if ( this.checkClickTarget(this.btnLeft) ) {
this.btnLeftClicked();
return;
}
if ( this.checkClickTarget(this.btnRight) ) {
this.btnRightClicked();
return;
}
if ( this.checkClickTarget(this.pic1) ) {
this.pic1Clicked();
return;
//
// Get the best item bounds to fit in the container. Param object must have
// width, height, itemCount, aspectRatio, maxRows, and minGap. The itemCount
// must be greater than 0. Result is single object with rowCount, colCount,
// itemWidth, and itemHeight.
getBestItemBounds(config) {
const actualRatio = config.width / config.height
// Just make up theoretical sizes, we just care about ratio
const theoreticalHeight = 100
const theoreticalWidth = theoreticalHeight * config.aspectRatio
// Go over each row count find the row and col count with the closest
// ratio.
let best
for (let rowCount = 1; rowCount <= config.maxRows; rowCount++) {
// Row count can't be higher than item count
if (rowCount > config.itemCount) continue
const colCount = Math.ceil(config.itemCount / rowCount)
// Get the width/height ratio
const ratio = (theoreticalWidth * colCount) / (theoreticalHeight * rowCount)
if (!best || Math.abs(ratio - actualRatio) < Math.abs(best.ratio - actualRatio)) {
best = { rowCount, colCount, ratio };
}
}
if ( this.checkClickTarget(this.pic2) ) {
this.pic2Clicked();
return;
// Build item height and width. If the best ratio is less than the actual ratio,
// it's the height that determines the width, otherwise vice versa.
const result = {itemHeight: 0, itemWidth: 0, rowCount: best.rowCount, colCount: best.colCount };
if (best.ratio < actualRatio) {
result.itemHeight = (config.height - (config.minGap * best.rowCount)) / best.rowCount;
result.itemWidth = result.itemHeight * config.aspectRatio;
} else {
result.itemWidth = (config.width - (config.minGap * best.colCount)) / best.colCount;
result.itemHeight = result.itemWidth / config.aspectRatio;
}
}
mapMove(event) {
}
mapUp(event) {
}
update() {
// ----------------------------------------------------------
this.animationId = window.requestAnimationFrame(this.update.bind(this));
// 清除画布内容
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// tween 更新动画
TWEEN.update();
// ----------------------------------------------------------
this.updateArr(this.renderArr);
}
return result;
}
// Change the item size via CSS property
resetContainerItems() {
const itemCount = document.querySelectorAll('.item').length
if (!itemCount) return
const container = this.boxContainer.nativeElement;
const rect = container.getBoundingClientRect()
// Get best item bounds and apply property
const { itemWidth, itemHeight } = this.getBestItemBounds({
width: rect.width,
height: rect.height,
itemCount,
aspectRatio: 16 / 12,
maxRows: 5,
minGap: 5
})
console.log('Item changes', itemWidth, itemHeight)
container.style.setProperty('--item-width', itemWidth + 'px')
container.style.setProperty('--item-height', itemHeight + 'px')
}
// Element resize support
// const resObs = new ResizeObserver(() => resetContainerItems())
// resObs.observe(document.getElementById('container'))
// Add item support
// let counter = 0
// document.getElementById('add').onclick = () => {
// const elem = document.createElement('div')
// elem.className = 'item'
// const button = document.createElement('button')
// button.innerText = 'Delete Item #' + (++counter)
// button.onclick = () => {
// document.getElementById('container').removeChild(elem)
// resetContainerItems()
// }
// elem.appendChild(button)
// document.getElementById('container').appendChild(elem)
// resetContainerItems()
// }
}
@mixin hide-overflow-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@mixin k-no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin k-img-bg {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
html, body {
width: 100%;
height: 100%;
font-size: 16px;
}
body {
background-color: #f0f2f5 !important;
}
* {
outline: none;
}
@font-face {
font-family: ChalkboardSE-Regular;
src: url('/assets/font/ChalkboardSE-Regular.woff') format('WOFF');
}
.k-16-9 {
position: relative;
width: 100%;
height: fit-content;
&:before {
content: "";
display: block;
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
}
.k-full-fill {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.k-flex-center {
display: flex;
align-items: center;
justify-content: center;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment