Skip to content

Commit

Permalink
✨ feat: 增加shape
Browse files Browse the repository at this point in the history
还有待实现功能
1. 一前一后两个点通过垂直交线封闭
2. 通过v-if实现即可以是图形,也可以当成clip-path用。
  • Loading branch information
aaron-yang-biz committed Jul 6, 2024
1 parent 5cdd8a9 commit b03b5a0
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 14 deletions.
240 changes: 240 additions & 0 deletions components/Shape.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<!--根据给定的点,产生一个平滑连接线的闭合图形-->
<!--catmull-room算法-->
<!--use https://bennettfeely.com/clippy/ custom polyon to edit vertex
TODO:
1. 增加部分直线点
2. 通过v-if,既可作为图形,也可作为clip-path

用作clip-path示例:

<Shape id="myCurve2" :scale=3.2 :dx=-400 :dy=-500>

```md
50% 99%
28% 81%
39% 67%
55% 55%
86% 63%
92% 52%
99% 45%
99% 100%
```
</Shape>

<style scoped>
.img-right {
position: absolute;
width: 50%;
height: 100%;
top: -10vh;
right:0;
/* clip-path: polygon(0 0, 100% 0%, 100% 100%); */
clip-path: url(#myCurve);
background: linear-gradient(to bottom, #f3b167, #ec38bc, #7303c0, #03001e);
filter: drop-shadow(-2px, 7px 4px rgba(0,0,0,0.5));
}
</style>
-->

<script setup>
import { computed, onMounted, ref } from 'vue'

const raw = ref(null)
const points = ref([])

const props = defineProps({
id: {
type: String,
required: true,
},
w: {
type: Number,
required: true,
},
h: {
type: Number,
required: true,
},
dy: {
type: Number,
default: 0
},
dx: {
type: Number,
default: 0
},
scaleX: {
type: Number,
default: null
},
scaleY: {
type: Number,
default: null
},
scale: {
type: Number,
default: 1
},
smoothing: {
type: Number,
default: 0.2
},
fill: {
type: String,
default: null
},
stroke: {
type: String,
default: null
},
strokeWidth: {
type: Number,
default: 0
}
})

// Render the svg <path> element
function getCurvePathData(points, smoothing = 0.2, closed = true) {
// append first 2 points for closed paths
if (closed) {
points = points.concat(points.slice(0, 2));
}

// Properties of a line
const line = (pointA, pointB) => {
const lengthX = pointB.x - pointA.x;
const lengthY = pointB.y - pointA.y;
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
};
};

// Position of a control point
const controlPoint = (current, previous, next, reverse) => {
const p = previous || current;
const n = next || current;
const o = line(p, n);

const angle = o.angle + (reverse ? Math.PI : 0);
const length = o.length * smoothing;

const x = current.x + Math.cos(angle) * length;
const y = current.y + Math.sin(angle) * length;
return { x, y };
};

let pathData = [];
pathData.push({ type: "M", values: [points[0].x, points[0].y] });

for (let i = 1; i < points.length; i++) {
let point = points[i];
const cp1 = controlPoint(points[i - 1], points[i - 2], point);
const cp2 = controlPoint(point, points[i - 1], points[i + 1], true);
//console.log( i, 'a', a)
const command = {
type: "C",
values: [cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y]
};

pathData.push(command);
}

// copy last commands 1st controlpoint to first curveto
if (closed) {
let comLast = pathData[pathData.length - 1];
let valuesLastC = comLast.values;
let valuesFirstC = pathData[1].values;

pathData[1] = {
type: "C",
values: [valuesLastC[0], valuesLastC[1], valuesFirstC.slice(2)].flat()
};
// delete last curveto
pathData = pathData.slice(0, pathData.length - 1);
}

return pathData;
};

// convert pathdata to d attribute string
function pathDataToD(pathData, decimals = 3) {
let d = pathData
.map((com) => {
return `${com.type}${com.values.map(value => { return +value.toFixed(decimals) }).join(" ")}`;
})
.join(" ");
return d;
}

/**
* if the points are specified by percentage, one need to convert it
* to absolute value before use.
* @param {*} points
* @param {*} width
* @param {*} height
*/
function scale(points, width, height, smoothing) {
let scaled = points.map(point => {
return [point[0] * width, point[1] * height]
});

let minX = Infinity;
let minY = Infinity;

for (let point of scaled) {
let [x, y] = point;

if (x < minX) minX = x;
if (y < minY) minY = y;
}

return scaled.map(point => {
return {
x: point[0] + props.dx,
y: point[1] + props.dy
}
})
}

onMounted(() => {
if ($renderContext.value === 'slide' && raw.value) {
let data = raw.value.textContent.split("\n")
let converted = data.map(point => {
let [x, y] = point.split(" ").map(parseFloat)
return [x / 100, y / 100]
})

console.log(converted)
points.value = converted
}
})

const path = computed(() => {
if (points.value.length === 0) {
return
}

let w = 500 * (props.scaleX || props.scale)
let h = 500 * (props.scaleY || props.scale)
let pathData = getCurvePathData(scale(points.value, w, h, props.smoothing), props.smoothing, true);

// serialize pathData to d attribute string
let d = pathDataToD(pathData, 1);
return d
})
</script>
<template>
<div ref="raw" style="display:none">
<slot />
</div>
<div v-bind="$attrs">
<svg width="0" height="0">
<defs>
<clipPath :id="$props.id">
<path :d="path" :fill="$props.fill" :stroke="$props.stroke" :stroke-width="$props.strokeWidth" />
</clipPath>
</defs>
</svg>
</div>
</template>
2 changes: 1 addition & 1 deletion components/SoarText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ watchEffect(() => {
cum += item.fontSize + 30
animes[i].style.top = view.value.height - cum - props.offsetY + "px"
console.log("calc top:", animes[i].text, view.value.height - cum - props.offsetY + "px")
console.debug("calc top:", animes[i].text, view.value.height - cum - props.offsetY + "px")
}
})
Expand Down
3 changes: 0 additions & 3 deletions components/card/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,8 @@ const props = defineProps({
const boxStyle = computed(() => {
// var borderColor = tinycolor(props.color).darken(20).toString();
return {
"position": "absolute",
// "border-left": `.2rem solid ${borderColor}`,
"margin": "1.5625em 0",
"padding": "0 1.2rem 1rem 1.2rem",
// "border-left": .4rem solid rgba(68, 138, 255, .8);
"box-shadow": "0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12), 0 3px 1px -2px rgba(0, 0, 0, .2)",
"border-radius": ".2rem",
"background-color": "rgba(255, 255, 255, 0.05)",
Expand Down
3 changes: 3 additions & 0 deletions components/card/FloatingCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ onMounted(async () => {

observer = new ResizeObserver(entries => {
for (let entry of entries) {
// todo: use entry.borderBoxSize instead
const { width, height, top, left } = entry.contentRect;
if (width === 0 || height === 0) {
continue
}

console.log("entry is", entry)

let cx = width * 0.15
let cy = height * 0.1
view.value = {
Expand Down
12 changes: 2 additions & 10 deletions vite.config.ts → setup/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import MdItAdmon from 'markdown-it-admon';
import { full as emoji } from 'markdown-it-emoji';
// import UnoCSS from 'unocss/vite';
import { defineConfig } from 'vite';


export default defineConfig({
slidev: {
vue: {
/* vue options */
template:{
template: {
compilerOptions: {
whitespace: 'preserve'
}
Expand All @@ -17,15 +15,9 @@ export default defineConfig({
markdown: {
/* markdown-it options */
markdownItSetup(md) {
/* custom markdown-it plugins */
// md.use(require('markdown-it-admon'));
// md.use(require('markdown-it-container'), 'takeaway');
md.use(emoji);
md.use(MdItAdmon);
},
},
}
// ,plugins:[
// UnoCSS()
// ]
}
})
1 change: 1 addition & 0 deletions utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function isShow(rng, click) {
/*
* css
*/

export function mergeTransform(baseTransform, additionalTransform) {
// If there's no base transform, just return the additional one.
if (!baseTransform) return additionalTransform;
Expand Down

0 comments on commit b03b5a0

Please sign in to comment.