vue delaunay实现狗脸变成猫脸变形动画效果代码
代码语言:html
所属分类:动画
代码描述:vue delaunay实现狗脸变成猫脸变形动画效果代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<style>
body {
margin: 0;
font-family: Georgia, sans-serif;
background: orange;
}
body h1 {
margin: 0;
padding: 1em 0;
text-align: center;
background: steelblue;
color: lightcyan;
text-shadow: 0.2em 0.2em 0 rgba(0, 0, 0, 0.2);
}
body button {
padding: 1em;
font: inherit;
}
body footer {
padding: 2em 1em;
background: orangered;
color: lightgoldenrodyellow;
}
body footer a {
color: gold;
}
#app {
text-align: center;
padding: .5em 0;
background: aliceblue;
}
#app .group {
display: inline-block;
vertical-align: top;
margin: .5em .8em;
}
#app .group button {
display: block;
margin: auto;
margin-bottom: .5em;
}
#app .image-container {
position: relative;
display: inline-block;
background: white;
}
#app .image-container .draw-area {
text-align: left;
overflow: hidden;
}
#app .image-container input {
position: absolute;
z-index: -1;
opacity: 0;
width: 0;
}
#app .image-container input + span {
position: relative;
display: block;
padding: .5em;
box-sizing: border-box;
background: purple;
color: white;
cursor: pointer;
}
#app .image-container input:focus + span, #app .image-container input:active + span,
#app .image-container input + span:hover {
background: darkviolet;
outline: 2px solid violet;
}
#app #morph {
position: relative;
display: table;
}
#app #morph canvas {
background: white;
}
#app #morph canvas + canvas {
position: absolute;
top: 0;
left: 0;
}
#app .image-container, #app #morph {
box-shadow: 0 0 0 1px paleturquoise;
}
#app svg {
position: absolute;
top: 0;
left: 0;
}
#app svg .connector {
stroke: rgba(30, 144, 255, 0.5);
stroke-width: 1;
stroke-dasharray: 2;
pointer-events: none;
}
#app svg [data-draggable] {
stroke: black;
fill: rgba(255, 255, 255, 0.2);
fill: url(#point-grad);
cursor: move;
}
#app svg [data-draggable].selected {
fill: rgba(255, 255, 0, 0.5);
}
#app pre {
-webkit-box-flex: 1;
flex: 1 1 auto;
margin: .5em 1em;
background: white;
color: #888;
border: 1px solid gainsboro;
overflow: auto;
}
</style>
</head>
<body>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/vue@2.6.1.js"></script>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/delaunay-fast.1.js"></script>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/abo-utils.0.3.js"></script>
<script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/drag-tracker.1.js"></script>
<h1>HyperMorph 3000™</h1>
<svg width="0" height="0" style="position:absolute;">
<defs>
<radialGradient id="point-grad">
<stop offset="80%" stop-color="transparent"/>
<stop offset="81%" stop-color="white"/>
</radialGradient>
</defs>
</svg>
<section id="app">
<div id="prepare" class="group">
<button @click="clear">Clear</button>
<div id="image1" class="image-container">
<div class="draw-area" :style="sizer()">
<canvas class="img"></canvas>
<triangulator :model="state.tri1" :selected-index="state.selectedIndex"
@added="onAdded" @selected="onSelected" @deleted="onDeleted"></triangulator>
</div>
<label>
<input type="file" accept="image/*">
<span>Change image</span>
</label>
</div>
<div id="image2" class="image-container">
<div class="draw-area" :style="sizer()">
<canvas class="img"></canvas>
<triangulator :model="state.tri2" :selected-index="state.selectedIndex"
@added="onAdded" @selected="onSelected" @deleted="onDeleted"></triangulator>
</div>
<label>
<input type="file" accept="image/*">
<span>Change image</span>
</label>
</div>
</div>
<div id="apply" class="group">
<button @click="warp">Morph</button>
<div id="morph">
<canvas id="c1" :width="state.size.w" :height="state.size.h"></canvas>
<canvas id="c2" :width="state.size.w" :height="state.size.h"></canvas>
</div>
</div>
<!---->
<pre>{{ state | prettyCompact }}</pre>
</section>
<!-- SVG UI -->
<script>
Vue.component('drag-node', {
template: '<circle data-draggable @dragging="onDragging" :cx="absCoord[0]" :cy="absCoord[1]" :r="r" />',
props: {
r: { default: 16 },
coord: Array,
//If 'coord' is relative to some other point:
offsetCenter: Array,
},
model: {
prop: 'coord',
event: 'do_it',
},
computed: {
absCoord() {
const point = this.coord,
center = this.offsetCenter,
absCoord = center ? [ point[0] + center[0], point[1] + center[1] ]
: point;
return absCoord;
},
},
methods: {
onDragging(e) {
const point = e.detail.pos,
center = this.offsetCenter,
relCoord = center ? [ point[0] - center[0], point[1] - center[1] ]
: point;
this.$emit('do_it', relCoord);
},
},
});
Vue.component('connector', {
template: '<line class="connector" :x1="start[0]" :y1="start[1]" :x2="absEnd[0]" :y2="absEnd[1]" />',
props: ['start', 'end', 'endIsRel'],
computed: {
absEnd() {
const start = this.start,
end = this.end,
absEnd = this.endIsRel ? [ start[0] + end[0], start[1] + end[1] ]
: end;
return absEnd;
}
}
});
</script>
<script>
/**
* Uses Delaunay triangulation to divide a rectangle into triangles.
*/
class Triangulator {
constructor(size, points) {
this.size = size;
this.points = points || [];
}
getEffectivePoints() {
const { w, h } = this.size,
corners = [
Triangulator.createPoint([0,0]),
Triangulator.createPoint([w,0]),
Triangulator.createPoint([0,h]),
Triangulator.createPoint([w,h]),
];
return corners.concat(this.points.filter(p => !p.toDelete));
}
getTriangles(indexes) {
const coords = this.getEffectivePoints().map(p => p.coord),
triangles = Delaunay.triangulate(coords),
trisList = [];
//"...it will return you a giant array, arranged in triplets,
// representing triangles by indices into the passed array."
let a, b, c;
for(let i = 0; i < triangles.length; i += 3) {
a = triangles[i];
b = triangles[i+1];
c = triangles[i+2];
trisList.push( indexes ? [a, b, c] : [coords[a], coords[b], coords[c]] );
}
return trisList;
}
getEdges() {
const drawn = {},
edges = [];
function addIfNew(p1, p2) {
var key = (p1 < p2) ? (p1 + '_' + p2) : (p2 + '_' + p1);
if(drawn[key]) { return; }
drawn[key] = true;
edges.push([p1, p2]);
}
this.getTriangles().forEach(t => {
addIfNew(t[0], t[1]);
addIfNew(t[1], t[2]);
addIfNew(t[2], t[0]);
});
return edges;
}
addPoint(coord) {
this.points.push(Triangulator.createPoint(coord));
}
static createPoint(coord) {
return {
coord: coord.map(Math.round),
//toDelete: false,
}
}
}
/**
* Renders an image on a canvas, within a maximum bounding box.
*/
class ImageRenderer {
constructor(canvas, onImgLoad) {
this.canvas = canvas;
const img = this.image = new Image();
img.addEventListener('load', e => {
const w = img.naturalWidth,
h = img.naturalHeight,
aspect = w/h;
this.info = {
width: w,
height: h,
aspect,
};
onImgLoad(this);
}, false);
}
setSrc(src) {
this.image.src = src;
}
clampSize(maxW, maxH) {
const info = this.info;
if(!info) { throw new Error(`No size info yet (${this.image.src})`); }
const w = info.width,
h = info.height,
shrinkageW = maxW / w,
shrinkageH = maxH / h,
shrinkage = Math.min(shrinkageW, shrinkageH),
clamped = (shrinkage < 1) ? [w * shrinkage, h * shrinkage] : [w, h];
return clamped;
}
render(canvSize) {
const canvas = this.canvas;
if(canvSize) {
canvas.width = canvSize[0];
canvas.height = canvSize[1];
}
const w = canvas.width,
h = canvas.height,
[imgW, imgH] = this.clampSize(w, h),
padW = (w - imgW) / 2,
padH = (h - imgH) / 2;
const ctx = canvas.getContext('2d');
ctx.drawImage(this.image, padW, padH, imgW, imgH);
}
}
/**
* Draws a warped image on a canvas by comparing a normal and a warped triangulation.
*/
function warpImage(img, triSource, triTarget, canvas, lerpT) {
const um = ABOUtils.Math,
uc = ABOUtils.Canvas,
ug = ABOUtils.Geom;
function drawTriangle(s1, s2, s3, d1, d2, d3) {
//TODO: Expand dest ~.5, and source similarly based on area difference..
//Overlap the destination areas a little
//to avoid hairline cracks when drawing mulitiple connected triangles.
const [d1x, d2x, d3x] = [d1, d2, d3], //ug.expandTriangle(d1, d2, d3, .3),
[s1x, s2x, s3x] = [s1, s2, s3]; //ug.expandTriangle(s1, s2, s3, .3);
uc.drawImageTriangle(img, ctx,
s1x, s2x, s3x,
d1x, d2x, d3x, true);
}
const { w, h } = triTarget.size,
ctx = canvas.getContext('2d'),
tri1 = triSource.getTriangles(true),
tri2 = triTarget.getTriangles(true),
co1 = triSource.getEffectivePoints().map(p => p.coord);
let co2 = triTarget.getEffectivePoints().map(p => p.coord);
if(lerpT || (lerpT === 0)) {
co2 = um.lerp(co1, co2, lerpT);
}
ctx.clearRect(0,0, w,h);
tri1.forEach((t1, i) => {
const corners1 = t1.map(i => co1[i]),
corners2 = t1.map(i => co2[i]);
drawTriangle(corners1[0], corners1[1], corners1[2],
corners2[0], corners2[1], corners2[2]);
});
}
(function() {
"use strict";
console.clear();
const um = ABOUtils.Math,
ud = ABOUtils.DOM,
[$, $$] = ud.selectors();
let _loader1, _loader2;
const _srcA = 'data:image/jpeg;base64,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.........完整代码请登录后点击上方下载按钮下载查看
网友评论0