Skip to content

Snap之Path

何波 edited this page Jul 14, 2019 · 3 revisions

[TOC]

Snap.path.getBBox(path)

let svg = Snap("#svg");
let path = svg.paper.path('M30 75 Q240 30, 300 120').attr({stroke:'black',fill:'none'})
console.info(path.attr('d'))

let d = path.attr('d')
let bound = Snap.path.getBBox(d);
console.dir(bound);

svg.rect(bound.x, bound.y, bound.width,bound.height).attr({stroke:'red',fill:'none'})

Snap.path.getPointAtLength(path, length)

let svg = Snap("#svg");
let path = svg.paper.path('M30 75 Q240 30, 300 120').attr({stroke:'black',fill:'none'})
let d = path.attr('d')

let total = Snap.path.getTotalLength(d)

let p = Snap.path.getPointAtLength(d, 0)
let circle = svg.circle(p.x, p.y, 5).attr({stroke:'red',fill:'none'})

Snap.animate(0, total, function (val) {
    let p = Snap.path.getPointAtLength(d, val)
    circle.attr({
        cx: p.x,
        cy: p.y
    });
}, 1000, mina.bounce)

Snap.path.getSubpath(path, from, to)

Returns the subpath of a given path between given start and end lengths

let svg = new Snap('#svg')

let path = svg.path("M 25,40 Q 150,150 200,150 T 375,40 Z")
    .attr({fill: 'none', stroke: "blue", strokeWidth: 2})

let pathStng = path.attr("d")
let subPathStg = Snap.path.getSubpath(pathStng, 100, 350)
console.info(subPathStg)
let subPath = svg.path(subPathStg)
    .attr({id: 'subPath', fill: 'none', stroke: "red", strokeWidth: 3})

Snap.path.getTotalLength(path)

Returns the length of the path in pixels (only works for path elements)

let svg = new Snap('#svg')

let path = svg.path("M 25,40 Q 150,150 200,150 T 375,40 Z")
    .attr({fill: 'none', stroke: "blue", strokeWidth: 2})

let pathStng = path.attr("d")
let totalLen = Snap.path.getTotalLength(pathStng)
console.info(totalLen)

Snap.path.map(path, matrix)

  • path (string) path string
  • matrix (object) see @Matrix

Transform the path string with the given matrix

path通过变换后,其MoveTo等属性相对来说变化了,但其realPath不会变化,通过map函数,返回经matrix后变化后的新path

let svg = new Snap('#svg')

let path = svg.path("M 25,25 Q 150,190 200,190 T 375,25 Z")
    .attr({fill: 'none', stroke: 'blue', strokeWidth: 2})

let pathString = path.attr("d")

let myMatrix = Snap.matrix()
myMatrix.scale(.8, .5)
myMatrix.rotate(20)
myMatrix.translate(100, 50)

let d = Snap.path.map(pathString, myMatrix)
path.attr("d", d)

Snap.path.intersection(path1, path2)

let svg = Snap("#svg");
let path1 = svg.paper.path('M90 140 Q240 30, 300 120').attr({stroke:'green','stroke-width': 1, fill:'none'})
let path2 = svg.paper.path('M30 100 Q 80 30, 100 100 T 200 80').attr({stroke:'blue','stroke-width': 1, fill:'none'})
let d1 = path1.attr('d')
let d2 = path2.attr('d')

// let inters = Snap.path.intersection(path1, path2); 也可以
let inters = Snap.path.intersection(d1, d2);


inters.forEach((v) => {
    let circle = svg.circle(v.x, v.y, 10)
})

拖动两条曲线,动态获得相交点(还不完善)

    let svg = Snap("#svg");
    let path1 = svg.paper.path('M90 140 Q240 30, 300 120').attr({stroke:'green','stroke-width': 10, fill:'none', id: 'path1'})
    let path2 = svg.paper.path('M30 100 Q 80 30, 100 100 T 200 80').attr({stroke:'blue','stroke-width': 10, fill:'none', id: 'path2'})
    let d1 = path1.attr('d')
    let d2 = path2.attr('d')
    let origTransform

    let dd1

    let set = new Snap.set()

    function onMove(dx, dy) {
        this.attr({
            transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
        })

        // 非常棒,可以把path中的d属性和matrix后转成matrix后的d

        let changePath

        if (this.attr('id') === 'path1') {
            changePath = d1
        }

        if (this.attr('id') === 'path2') {
            changePath = d2
        }

        let pathTransform = Snap.path.map(changePath, this.matrix)
        dd1 = pathTransform.toString()

        let dd2 = d1 === changePath ? d2 : d1

        let inters = Snap.path.intersection(dd1, dd2);

        // 这里用set操作,非常方便
        set.forEach((ele) => {
            ele.remove()
        })
        set.clear()

        inters.forEach((v) => {
            let circle = svg.circle(v.x, v.y, 10)
            set.push(circle)
        })

    }

    function onStart () {
        origTransform = this.transform().local;
    }

    function onEnd () {
        if (this.attr('id') === 'path1') {
            d1 = dd1
        }
    }

    function onEnd2 () {
        if (this.attr('id') === 'path2') {
            d2 = dd1
        }
    }

    path1.drag(onMove, onStart, onEnd)
    path2.drag(onMove, onStart, onEnd2)

Snap.path.isBBoxIntersect(bbox1, bbox2)

  • bbox1 (string) first bounding box
  • bbox2 (string) second bounding box

Returns true if two bounding boxes intersect

let svg = new Snap('#svg')
let origTransform

let path1 = svg.path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80")
    .attr({fill: 'none', stroke: 'blue', strokeWidth: 5})

let path2 = svg.path("M80 150 L 240 110 L 265 90, L 200 90,Z")
    .attr({fill: 'none', stroke: 'green', strokeWidth: 5})

let bb1 = path1.getBBox()
let bb2 = path2.getBBox()

let rect1 = svg.paper.rect(bb1.x, bb1.y, bb1.width, bb1.height).attr({fill: 'none', stroke: 'blue', strokeWidth: 1, strokeDasharray: '8,8'})
let rect2 = svg.paper.rect(bb2.x, bb2.y, bb2.width, bb2.height).attr({fill: 'none', stroke: 'green', strokeWidth: 1, strokeDasharray: '8,8'})

let g1 = svg.paper.g(rect1, path1)
let g2 = svg.paper.g(rect2, path2)


function onMove(dx, dy, x, y) {
    this.attr({
        transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
    })

    let pathTransform = Snap.path.map(this.realPath, this.matrix).toString()


    bb1 = Snap.path.getBBox(pathTransform)
    bb2 = path2.getBBox()

    let isBBInterset = Snap.path.isBBoxIntersect(bb1, bb2)
    console.info(isBBInterset)
}


function onStart() {
    origTransform = this.transform().local;
}

g1.drag(onMove, onStart)

Snap.path.isPointInside(path, x, y)

  • x (number) x of the point
  • y (number) y of the point

Returns true if given point is inside a given closed path

let svg = new Snap('#svg')
let origTransform


let path = svg.path("M 25,25 Q 150,190 200,190 T 375,25 Z")
    .attr({fill: 'none', stroke: 'blue', strokeWidth: 2});

function onMove(dx, dy, x, y) {
    this.attr({
        transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
    })
    
    // 注意把body的margin,padding设为0
    let pntInside = Snap.path.isPointInside(path.attr("d"), x, y)
    console.info(pntInside)
}

function onStart () {
    origTransform = this.transform().local;
}
svg.circle(200, 100, 4).attr({fill: 'red'}).drag(onMove, onStart)

Snap.path.isPointInsideBBox(bbox, x, y)

  • bbox (string) bounding box
  • x (string) x coordinate of the point
  • y (string) y coordinate of the point

Returns true if given point is inside bounding box

let svg = new Snap('#svg')
let origTransform

let path = svg.path("M 25,25 Q 150,190 200,190 T 375,25 Z")
    .attr({fill: 'none', stroke: 'blue', strokeWidth: 2});
let bb = path.getBBox()
svg.paper.rect(bb.x,bb.y,bb.width, bb.height).attr({
    fill: 'none',
    'stroke': 'green',
    strokeDasharray: '8,8'
})

function onMove(dx, dy, x, y) {
    this.attr({
        transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
    })

    let pntInsideBB = Snap.path.isPointInsideBBox(bb, x, y)
    console.info(pntInsideBB)
}


function onStart() {
    origTransform = this.transform().local;
}

svg.circle(200, 100, 4).attr({fill: 'red'}).drag(onMove, onStart)

http://svgdiscovery.com/Snap.svg/08-snap-path-methods.htm

http://svgdiscovery.com/Snap.svg/Snap.svg-API/Snap.svg-API-Examples.htm

Snap.path.bezierBBox(…)

  • p1x 数值。曲线第1个点的x位置。
  • p1y 数值。曲线第1个点的y位置。
  • c1x 数值。曲线第1个锚的x位置。
  • c1y 数值。曲线第1个锚的y位置。
  • c2x 数值。曲线第2个锚的x位置。
  • c2y 数值。曲线第2个锚的y位置。
  • p2x 数值。曲线第2个点的x位置。
  • p2y 数值。曲线第2个点的y位置。

或者

  • bez 数组。包含6个点的贝塞尔曲线数组。

返回:

{
    x:数值。盒子左上点的x坐标。
    y:数值。盒子左上点的y坐标。
    x2:数值。盒子右下点的x坐标。
    y2:数值。盒子右下点的y坐标。
    width:数值。盒子的宽度。
    height:数值。盒子的高度。
}

返回一个给定的三次贝塞尔曲线的边界框

let svg = new Snap('#svg')
let path = svg.path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80").attr({
    fill: 'none',
    stroke: 'blue',
    strokeWidth: 3
});

let pathStng = path.attr("d")
let pathSegments = Snap.parsePathString(pathStng)
/* pathSegments
[
    [M,10,80]
    [C,40,10,65,10,95,80]
    [S,150,150,180,80]
]
--------------------------
[
    [M,p1x,p1y]
    [C,c1x,c1y,c2x,c2y,p2x,p2y]
    [S,150,150,180,80]
]
*/
let p1x = pathSegments[0][1]
let p1y = pathSegments[0][2]
let c1x = pathSegments[1][1]
let c1y = pathSegments[1][2]
let c2x = pathSegments[1][3]
let c2y = pathSegments[1][4]
let p2x = pathSegments[1][5]
let p2y = pathSegments[1][6]
let bb = Snap.path.bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y)

let rect = svg.rect(bb.x, bb.y, bb.w, bb.h)
    .attr({fill: 'none', stroke: "black", strokeWidth: 1})

Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t)

function screenPath(pathSnap) {
    let path = pathSnap.node
    let sCTM = path.getCTM()
    let svgRoot = path.ownerSVGElement
    let pathStng = pathSnap.attr("d")

    let segList = Snap.parsePathString(pathStng)
    let segs = segList.length
    //---change segObj values
    for (let k = 0; k < segs; k++) {
        let segObj = segList[k]
        let sol = segObj.length
        let cmd = segObj[0]

        if (cmd != "A" && cmd != "a") {
            for (let n = 1; n < sol; n++) {
                let mySVGPoint = svgRoot.createSVGPoint();
                mySVGPoint.x = segObj[n]
                mySVGPoint.y = segObj[n + 1]
                mySVGPointTrans = mySVGPoint.matrixTransform(sCTM)
                segObj[n] = mySVGPointTrans.x
                segObj[n + 1] = mySVGPointTrans.y
                n++
            }
        }
    }

    pathSnap.attr({d: segList.toString()})
    path.removeAttribute("transform")
}


let svg = Snap("#svg");


// let path1 = svg.path("M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80").attr({id: 'path1', fill: 'none', stroke: 'green', strokeWidth: 2, transform: ("t(100,60) s(2,1)")});

// screenPath(path1) // 可以把上面transform后转成下面的path

let path1 = svg.path("M25,140C85,70,135,70,195,140S305,210,365,140").attr({
    id: 'path1',
    fill: 'none',
    stroke: 'green',
    strokeWidth: 2
})

let pathStng = path1.attr("d")
let pathSegments = Snap.parsePathString(pathStng)

console.info(pathSegments)


/* pathSegments
[
    ["M", 25, 140]
    ["C", 85, 70, 135, 70, 195, 140]
    ["S", 305, 210, 365, 140]
]
--------------------------
[
    [M,p1x,p1y]
    [C,c1x,c1y,c2x,c2y,p2x,p2y]
    [S,305, 210, 365, 140]
]
*/

let p1x = pathSegments[0][1]
let p1y = pathSegments[0][2]
let c1x = pathSegments[1][1]
let c1y = pathSegments[1][2]
let c2x = pathSegments[1][3]
let c2y = pathSegments[1][4]
let p2x = pathSegments[1][5]
let p2y = pathSegments[1][6]

let dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, 0)
svg.circle(dots.x, dots.y, 4).attr({fill: 'black'})
console.info(dots)

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, .2)
svg.circle(dots.x, dots.y, 4).attr({fill: 'orange'})

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, .4)
svg.circle(dots.x, dots.y, 4).attr({fill: 'violet'})

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, .5)
svg.circle(dots.x, dots.y, 4).attr({fill: 'red'})

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, .6)
svg.circle(dots.x, dots.y, 4).attr({fill: 'lime'})

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, .8)
svg.circle(dots.x, dots.y, 4).attr({fill: 'aqua'})

dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, 1)
svg.circle(dots.x, dots.y, 4).attr({fill: 'white'})

Snap.animate(0, 1, function (val) {
    dots = Snap.path.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,val)
    anCircle.attr({
        cx: dots.x,
        cy: dots.y
    })
}, 1000)

Snap.path.toAbsolute(path) 小写转大写

Converts path coordinates into absolute values

let svg = new Snap('#svg')
let dRel = "M25,140c60,-70,110,-70,170,0s110,70,170,0"
let relPath = svg.path(dRel).attr({strokeWidth: 3, stroke: 'black', strokeDasharray: "8,8", fill: 'none'})

let dAbs = Snap.path.toAbsolute(dRel)
console.info(dAbs.toString()) // 大写
let absPath = svg.path(dAbs).attr({strokeWidth: 2, stroke: 'red', fill: 'none'})

Snap.path.toRelative(path) 大写转小写

Converts path coordinates into relative values

let svg = new Snap('#svg')

let dAbs = 'M25,140 C 85,70 135,70 195,140 S 305,210 365,140'
let absPath = svg.path(dAbs).attr({strokeWidth: 3, stroke: 'black', strokeDasharray: "8,8", fill: 'none'})

let dRel = Snap.path.toRelative(dAbs)
console.info(dRel.toString()) // 小写
let relPath = svg.path(dRel).attr({strokeWidth: 2, stroke: 'red', fill: 'none'})

Snap.path.toCubic(pathString)

转换一个路径为一个全部路径段采用三次贝塞尔曲线表示的新路径

let svg = new Snap('#svg')

let path = svg.path("M 25,25 Q 150,190 200,190 T 375,25 Z")
    .attr({fill: 'none', stroke: 'blue', strokeWidth: 4, strokeDasharray: '8,8'})

let pathString = path.attr("d")
let pathStringCubic = Snap.path.toCubic(pathString)
console.info(pathStringCubic.toString())
let cubicPath = svg.path(pathStringCubic).attr({strokeWidth: 2, stroke: 'red', fill: 'none'})

Clone this wiki locally