Commit f6eba663 authored by liujiaxin's avatar liujiaxin

1

parent 8a51c964
...@@ -30,7 +30,10 @@ ...@@ -30,7 +30,10 @@
], ],
"styles": [ "styles": [
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", "./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": [] "scripts": []
}, },
......
@import '../style/common_mixin.css';
.model-content {
width: 100%;
height: 100%;
}
<nz-form-item>
<div class="model-content"> <nz-form-label [nzSpan]="4">Vocabulary</nz-form-label>
<nz-form-control [nzSpan]="8">
<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 <app-upload-image-with-preview
[picUrl]="item.pic_url" [picUrl]="item.contentObj.pic_id"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')" (imageUploaded)="onHandleQuestionImage($event)"
></app-upload-image-with-preview> ></app-upload-image-with-preview>
<input type="text" nz-input placeholder="Please input title"
<app-audio-recorder [(ngModel)]="item.contentObj.title" (blur)="updateQuestionTitle()">
[audioUrl]="item.audio_url" </nz-form-control>
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')" </nz-form-item>
></app-audio-recorder> <nz-form-item>
<app-custom-hot-zone></app-custom-hot-zone> <nz-form-label [nzSpan]="4">Option</nz-form-label>
<app-upload-video></app-upload-video> <nz-form-control [nzSpan]="20">
<app-lesson-title-config></app-lesson-title-config> <button
</div> nz-button
nzType="default"
(click)="addNewOptionItem()"
</div> [disabled]="options.length >= 4"
>
\ No newline at end of file <i class="anticon anticon-plus-circle-o"></i>
Add
</button>
<div class="opt-container">
<div
class="opt-wrap"
*ngFor="let item of options; let i = index"
>
<div class="tool-bar">
<label nz-checkbox [ngModel]="item.right" (click)="setRightAnswer(i)" class="right-answer">
Right
</label>
<div class="button-group">
<button
nz-button
nzType="primary"
nzSize="small"
nzShape="circle"
style="margin-right: 1rem;"
(click)="onHandleSwitchContent(item)"
>
<i class="anticon anticon-retweet"></i>
</button>
<nz-dropdown [nzTrigger]="'click'">
<button
nz-button
nzType="primary"
nzSize="small"
nzShape="circle"
nz-dropdown
>
<i class="anticon anticon-setting"></i>
</button>
<ul nz-menu>
<li nz-menu-item (click)="onMovePage(i, -1)" [class.disabled]="i < 1">
<i class="anticon anticon-arrow-up"></i>
Move Up
</li>
<li nz-menu-item (click)="onMovePage(i, 1)" [class.disabled]="i + 1 >= options.length">
<i class="anticon anticon-arrow-down"></i>
Move Down
</li>
<li nz-menu-item (click)="onDeleteCoursewareItem(i)">
<i class="anticon anticon-delete"></i>
Delete
</li>
</ul>
</nz-dropdown>
</div>
</div>
<div class="content-editor">
<input *ngIf="item.type === OPTION_TYPE.TEXT" type="text" nz-input [(ngModel)]="item.text"
placeholder="Please input title" (blur)="updateOptionItemText(i)">
<app-upload-image-with-preview
*ngIf="item.type === OPTION_TYPE.PIC"
[picUrl]="item.pic_id"
(imageUploaded)="onHandleOptionItemImage($event, i)"
></app-upload-image-with-preview>
<app-audio-recorder *ngIf="item.type === OPTION_TYPE.AUDIO"
[audioUrl]="item.audio_id"
(audioUploaded)="onHandleOptionItemAudio($event, i)">
</app-audio-recorder>
</div>
</div>
</div>
</nz-form-control>
</nz-form-item>
.opt-container {
width: 100%;
display: flex;
flex-wrap: wrap;
.opt-wrap {
margin: 0 1rem 1rem 0;
display: flex;
flex-direction: column;
height: 14rem;
width: 20rem;
border: 2px dashed #ddd;
border-radius: 0.5rem;
padding: 0.8rem;
.tool-bar {
display: flex;
justify-content: space-between;
align-items: center;
}
.content-editor {
height: 100%;
}
}
}
.disabled {
pointer-events: none;
opacity: 0.6;
}
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core'; import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import * as _ from 'lodash';
import { NzModalService } from 'ng-zorro-antd';
@Component({ @Component({
selector: 'app-form', selector: 'app-form',
templateUrl: './form.component.html', templateUrl: './form.component.html',
styleUrls: ['./form.component.css'] styleUrls: ['./form.component.scss']
}) })
export class FormComponent implements OnInit, OnChanges, OnDestroy { export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用 // 储存数据用
saveKey = "test_0011"; saveKey = 'ww_question';
// 储存对象 // 储存对象
item; item;
originContent;
options = [];
OPTION_TYPE = {TEXT: 1, PIC: 2, AUDIO: 3};
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) { constructor(private appRef: ApplicationRef,
private modalService: NzModalService,
private changeDetectorRef: ChangeDetectorRef) {
} }
ngOnInit() { ngOnInit() {
this.item = {}; this.item = {
contentObj: {}
};
// 获取存储的数据 // 获取存储的数据
(<any> window).courseware.getData((data) => { (window as any).courseware.getData((data) => {
if (data) { if (data) {
this.item = data; this.item = data;
this.originContent = {...this.item.contentObj};
this.options = _.get(this.item.contentObj, 'options', []);
// Ken 2019-04-14 17:41 把showPic, showText的逻辑换为type
_.forEach(this.options, o => {
if (_.isUndefined(o.type)) {
o.type = o.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT;
}
});
} }
this.init(); this.init();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
this.changeDetectorRef.detectChanges(); this.changeDetectorRef.detectChanges();
...@@ -54,25 +71,18 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -54,25 +71,18 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
} }
/** // onImageUploadSuccess(e, key) {
* 储存图片数据 //
* @param e // this.item[key] = e.url;
*/ // this.save();
onImageUploadSuccess(e, key) { // }
this.item[key] = e.url;
this.save();
}
/**
* 储存音频数据
* @param e
*/
onAudioUploadSuccess(e, key) {
this.item[key] = e.url; // onAudioUploadSuccess(e, key) {
this.save(); //
} // this.item[key] = e.url;
// this.save();
// }
...@@ -80,7 +90,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -80,7 +90,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存数据 * 储存数据
*/ */
save() { save() {
(<any> window).courseware.setData(this.item, null, this.saveKey); (window as any).courseware.setData(this.item, null, this.saveKey);
this.refresh(); this.refresh();
} }
...@@ -93,5 +103,86 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -93,5 +103,86 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}, 1); }, 1);
} }
onHandleQuestionImage(e) {
this.item.contentObj.pic_id = e.url;
this.save();
}
updateQuestionTitle() {
this.save();
}
onHandleOptionItemImage(e, index) {
this.options[index].pic_id = e.url;
this.item.contentObj.options = this.options;
this.save();
}
onHandleOptionItemAudio(e, index) {
this.options[index].audio_id = e.url;
this.item.contentObj.options = this.options;
this.save();
}
updateOptionItemText(index) {
this.item.contentObj.options = this.options;
this.save();
}
setRightAnswer(index) {
this.options[index].right = !this.options[index].right;
this.item.contentObj.options = this.options;
this.save();
}
addNewOptionItem() {
const content = {
text: '',
pic_id: null,
right: false,
type: this.OPTION_TYPE.TEXT,
};
this.options.push(content);
}
onHandleSwitchContent(item) {
switch (item.type) {
case this.OPTION_TYPE.TEXT:
item.type = this.OPTION_TYPE.PIC;
break;
case this.OPTION_TYPE.PIC:
item.type = this.OPTION_TYPE.AUDIO;
break;
case this.OPTION_TYPE.AUDIO:
item.type = this.OPTION_TYPE.TEXT;
break;
}
this.item.contentObj.options = this.options;
this.save();
}
onMovePage(index, direction) {
const temp = this.options[index + direction];
this.options[index + direction] = this.options[index];
this.options[index] = temp;
this.options = [...this.options];
this.item.contentObj.options = this.options;
this.save();
}
onDeleteCoursewareItem(index) {
this.modalService.confirm({
nzTitle: 'Are you sure delete this?',
nzOkText: 'Yes',
nzOkType: 'danger',
nzCancelText: 'No',
nzOnOk: () => {
this.options.splice(index, 1);
this.item.contentObj.options = this.options;
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> <div class="question-player">
<canvas id="canvas" #canvas></canvas> <div class="top-part">
<img
*ngIf="data.contentObj.pic_id"
[src]="data.contentObj.pic_id "
alt="cover"
(dragstart)="false;$event.preventDefault();"
(click)="onHandleZoomIn()"
[@zoom]="zoomIn ? 'in' : 'none'"
/>
<div class="title">{{data.contentObj.title}}</div>
</div>
<div class="opt-container">
<div
class="opt-wrap"
*ngFor="let item of options; let i = index"
[@tip-border]="item.state || 'none'"
(click)="handleAnimation(item)"
>
<span class="opt-circle" [@tip-circle]="item.state || 'none'">{{transCharCode(i)}}</span>
<div class="text" *ngIf="item.type === OPTION_TYPE.TEXT">{{item.text}}</div>
<div class="audio" style="text-align: center" *ngIf="item.type === OPTION_TYPE.AUDIO" (click)="playAudio(item, $event)">
<i class="anticon anticon-sound"></i>
</div>
<img *ngIf="item.type === OPTION_TYPE.PIC" [src]="item.pic_id" alt=""
(dragstart)="false;$event.preventDefault();"/>
</div>
</div>
<!--<img src="/assets/question/left.png" alt="ufo" class="left-ufo" (dragstart)="false;$event.preventDefault();">-->
<!--<img src="/assets/question/right.png" alt="ufo" class="right-ufo" (dragstart)="false;$event.preventDefault();">-->
</div> </div>
.question-player {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
background-image: url("../../assets/common_bg.jpg");
background-size: cover;
background-repeat: no-repeat;
.top-part {
height: 35%;
min-width: 60%;
max-width: 90%;
display: flex;
justify-content: center;
align-items: center;
img {
//width: 40%;
height: 60%;
margin-right: 1.5rem;
object-fit: contain;
}
.title {
font-size: 2.5rem;
color: #ffb00f;
background-color: white;
padding: 0.5rem 1rem;
border-radius: 1rem;
}
}
.opt-container {
height: 55%;
width: 80%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
.opt-wrap {
width: 30%;
height: 45%;
position: relative;
background-color: #ffffff;
margin: 1rem;
border-radius: 0.8rem;
display: flex;
justify-content: center;
align-items: center;
border: 0.3rem solid #ffffff;
.opt-circle {
width: 2.5rem;
height: 2.5rem;
background-color: #ffffff;
position: absolute;
border-radius: 50%;
left: -1rem;
top: -1rem;
text-align: center;
line-height: 2.5rem;
font-size: 24px;
font-weight: 500;
}
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.text {
font-size: 28px;
font-weight: 500;
}
.audio {
color: white;
background-color: #00BCD4;
border-radius: 5px;
font-size: 5vh;
font-weight: 500;
width: 13vh;
cursor: pointer;
}
}
}
.left-ufo {
width: 10%;
object-fit: contain;
position: absolute;
left: 15vh;
bottom: 20vh;
}
.right-ufo {
width: 10%;
object-fit: contain;
position: absolute;
right: 15vh;
bottom: 20vh;
}
}
import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core'; import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core';
import * as _ from 'lodash';
import { import {
Label, Label,
MySprite, tweenChange, MySprite, tweenChange,
...@@ -11,6 +11,7 @@ import {Subject} from 'rxjs'; ...@@ -11,6 +11,7 @@ import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators'; import {debounceTime} from 'rxjs/operators';
import TWEEN from '@tweenjs/tween.js'; import TWEEN from '@tweenjs/tween.js';
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
...@@ -18,662 +19,161 @@ import TWEEN from '@tweenjs/tween.js'; ...@@ -18,662 +19,161 @@ import TWEEN from '@tweenjs/tween.js';
@Component({ @Component({
selector: 'app-play', selector: 'app-play',
templateUrl: './play.component.html', templateUrl: './play.component.html',
styleUrls: ['./play.component.css'] styleUrls: ['./play.component.scss'],
animations: [
trigger('tip-border', [
state('right', style({border: '0.3rem solid #52E55D', 'background-color': '#52E55D', transform: 'scale(1)'})),
state('none', style({transform: 'scale(1)'})),
state('wrong', style({border: '0.3rem solid #ff3333', 'background-color': '#ff3333', transform: 'scale(1)'})),
transition('none => wrong', animate('.2s ease-in', keyframes([
style({transform: 'scale(1)'}),
style({transform: 'scale(0.6)'}),
style({transform: 'scale(1)'}),
]))), // 第一个参数:动画时间, 第二个参数:动画延迟时间
transition('none => right', animate('.2s ease-in', keyframes([
style({transform: 'scale(1)'}),
style({transform: 'scale(0.6)'}),
style({transform: 'scale(1)'}),
]))),
transition('wrong => none', animate('.2s ease-out'))
]),
trigger('tip-circle', [
state('right', style({'background-color': '#52E55D'})),
state('none', style({})),
state('wrong', style({'background-color': '#ff3333'})),
transition('none => wrong', animate('.2s ease-in')), // 第一个参数:动画时间, 第二个参数:动画延迟时间
transition('none => right', animate('.2s ease-in')),
transition('wrong => none', animate('.2s ease-out'))
]),
trigger('zoom', [
state('in', style({
position: 'absolute', 'z-index': '1', top: '0', left: '0', right: '0', bottom: '0', margin: 'auto',
height: '60%', 'object-fit': 'contain', 'max-width': '80%'
})),
state('none', style({})),
transition('none => in', animate('.3s', keyframes([
style({
height: '10%', position: 'absolute', 'z-index': '1', top: '0', left: '0',
right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%',
opacity: '0'
}),
style({
height: '20%', position: 'absolute', 'z-index': '1', top: '0', left: '0',
right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%',
opacity: '0.2'
}),
style({
height: '30%', position: 'absolute', 'z-index': '1', top: '0', left: '0',
right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%',
opacity: '0.4'
}),
style({
height: '40%', position: 'absolute', 'z-index': '1', top: '0', left: '0',
right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%',
opacity: '0.6'
}),
style({
height: '50%', position: 'absolute', 'z-index': '1', top: '0', left: '0',
right: '0', bottom: '0', margin: 'auto', 'object-fit': 'contain', 'max-width': '80%',
opacity: '0.8'
}),
]))), // 第一个参数:动画时间, 第二个参数:动画延迟时间
transition('in => none', animate('.5s ease-out'))
])
]
}) })
export class PlayComponent implements OnInit, OnDestroy { export class PlayComponent implements OnInit, OnDestroy {
saveKey = 'ww_question';
@ViewChild('canvas', {static: true }) canvas: ElementRef; options;
@ViewChild('wrap', {static: true }) wrap: ElementRef; zoomIn;
rightAudio = new Audio();
wrongAudio = new Audio();
OPTION_TYPE = {TEXT: 1, PIC: 2, AUDIO: 3};
optionAudio = new Audio();
// 数据 // 数据
data; data;
constructor() {
ctx; this.rightAudio.src = '/assets/right.mp3';
this.rightAudio.load();
canvasWidth = 1280; // canvas实际宽度 this.wrongAudio.src = '/assets/wrong.mp3';
canvasHeight = 720; // canvas实际高度 this.wrongAudio.load();
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;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.winResizeEventStream.next();
} }
ngOnInit() {
this.data = {}; ngOnInit() {
this.data = {
contentObj: {}
};
this.zoomIn = false;
this.options = _.get(this.data.contentObj, 'options', []).map(item => {
item.state = '';
// Ken 2019-04-15 22:38 把showPic, showText的逻辑换为type
if (_.isUndefined(item.type)) {
item.type = item.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT;
}
return item;
});
// 获取数据 // 获取数据
const getData = (<any> window).courseware.getData; const getData = (window as any).courseware.getData;
getData((data) => { getData((data) => {
if (data && typeof data == 'object') { if (data && typeof data === 'object') {
this.data = data; this.data = data;
this.zoomIn = false;
this.options = _.get(this.data.contentObj, 'options', []).map(item => {
item.state = '';
// Ken 2019-04-15 22:38 把showPic, showText的逻辑换为type
if (_.isUndefined(item.type)) {
item.type = item.showPic ? this.OPTION_TYPE.PIC : this.OPTION_TYPE.TEXT;
}
return item;
});
} }
// console.log('data:' , data);
// 初始化 各事件监听
this.initListener();
// 若无数据 则为预览模式 需要填充一些默认数据用来显示
this.initDefaultData();
// 初始化 音频资源
this.initAudio();
// 初始化 图片资源
this.initImg();
// 开始预加载资源
this.load();
if (window['air']) {
window['air'].hideAirClassLoading(this.saveKey, this.data);
}
}, this.saveKey); }, this.saveKey);
} }
ngOnDestroy() { ngOnDestroy() {
window['curCtx'] = null; this.rightAudio.pause();
window.cancelAnimationFrame(this.animationId); this.wrongAudio.pause();
} this.optionAudio.pause();
this.rightAudio = null;
this.wrongAudio = null;
load() { this.optionAudio = null;
// 预加载资源
this.loadResources().then(() => {
window["air"].hideAirClassLoading(this.saveKey, this.data);
this.init();
this.update();
});
}
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);
}
}
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();
};
}
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) {
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;
}
}
return false;
}
addUrlToAudioObj(key, url = null, vlomue = 1, loop = false, callback = null) {
const audioObj = this.audioObj;
if (url == null) {
url = key;
}
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();
};
}
}
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';
}
}
/**
* 添加预加载图片
*/
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() { transCharCode(index) {
return String.fromCharCode(65 + index);
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;
} }
handleAnimation(item) {
btnLeftClicked() { if (item.right) {
item.state = 'right';
this.lastPage(); this.rightAudio.play();
} } else {
item.state = 'wrong';
btnRightClicked() { this.wrongAudio.play();
setTimeout(() => {
this.nextPage(); item.state = '';
} }, 1500);
lastPage() {
if (this.curPic == this.pic1) {
return;
} }
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;
});
} }
nextPage() { onHandleZoomIn() {
this.zoomIn = !this.zoomIn;
if (this.curPic == this.pic2) {
return;
}
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() { playAudio(option, e) {
this.playAudio(this.data.audio_url); e.stopPropagation();
this.optionAudio.src = option.audio_id;
// this.optionAudio.load();
this.optionAudio.play();
} }
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;
}
if ( this.checkClickTarget(this.pic2) ) {
this.pic2Clicked();
return;
}
}
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);
}
} }
@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